Movie Theater Seat View

Movie Theater Seat View

by dockui

SwiftUI iOS movie ticket booking UI with seat selection grid, showtimes, date picker, pricing calculator, and modern dark cinema design

Platform
SwiftUI
Code
Native Swift
Format
Screen
Access
Free

Code Snippet

import SwiftUI

struct MovieTheaterSeatView: View {
    @State private var selectedDate = Date()
    @State private var seatCount = 2
    @State private var selectedShowtime: Showtime?
    @State private var selectedSeats: Set<String> = []
    @State private var showingDatePicker = false
    
    let movieTitle = "Stranger Things Finale"
    let theaterName = "AMC 42nd Street"
    let theaterLocation = "New York, NY"
    
    let showtimes = [
        Showtime(id: 1, time: "2:00 PM", available: true),
        Showtime(id: 2, time: "4:00 PM", available: true),
        Showtime(id: 3, time: "6:30 PM", available: true),
        Showtime(id: 4, time: "10:00 PM", available: true)
    ]
    
    // Seat map: rows A-F, columns 1-10
    let seatRows = ["A", "B", "C", "D", "E", "F"]
    let seatColumns = Array(1...10)
    
    // Pre-occupied seats
    let occupiedSeats: Set<String> = ["A3", "A4", "B7", "C2", "C3", "D5", "D6", "E8", "F1", "F2"]
    
    var totalPrice: Double {
        Double(selectedSeats.count) * 9.00
    }
    
    var body: some View {
        ZStack {
            // Dark background
            Color.black.ignoresSafeArea()
            
            ScrollView {
                VStack(spacing: 20) {
                    // Header Section
                    headerSection
                    
                    // Date and Seat Selection
                    controlsSection
                    
                    // Theater Location
                    locationSection
                    
                    // Screen and Seat Map
                    seatMapSection
                    
                    // Showtime Selection
                    showtimeSection
                    
                    // Buy Tickets Button
                    buyTicketsButton
                }
                .padding(.horizontal, 16)
                .padding(.bottom, 40)
                .frame(maxWidth: .infinity)
            }
        }
        .sheet(isPresented: $showingDatePicker) {
            DatePickerSheet(selectedDate: $selectedDate)
        }
    }
    
    private var headerSection: some View {
        VStack(spacing: 8) {
            Text(movieTitle)
                .font(.system(size: 28, weight: .bold))
                .foregroundColor(.white)
                .multilineTextAlignment(.center)
                .lineLimit(2)
                .minimumScaleFactor(0.8)
        }
    }
    
    private var controlsSection: some View {
        HStack(spacing: 12) {
            // Date Picker Button
            Button(action: { showingDatePicker = true }) {
                HStack(spacing: 8) {
                    Text(dateFormatter.string(from: selectedDate))
                        .font(.system(size: 16, weight: .medium))
                        .foregroundColor(.white)
                    Image(systemName: "chevron.down")
                        .font(.system(size: 12, weight: .semibold))
                        .foregroundColor(.white.opacity(0.7))
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 14)
                .background(Color.gray.opacity(0.2))
                .overlay(
                    RoundedRectangle(cornerRadius: 12)
                        .stroke(Color.white.opacity(0.3), lineWidth: 1)
                )
                .clipShape(RoundedRectangle(cornerRadius: 12))
            }
            
            // Seat Count Selector
            HStack(spacing: 16) {
                Button(action: { if seatCount > 1 { seatCount -= 1 } }) {
                    Image(systemName: "minus")
                        .font(.system(size: 14, weight: .semibold))
                        .foregroundColor(seatCount > 1 ? .white : .white.opacity(0.4))
                }
                
                Text("\(seatCount) Seats")
                    .font(.system(size: 16, weight: .medium))
                    .foregroundColor(.white)
                    .frame(minWidth: 80)
                
                Button(action: { if seatCount < 8 { seatCount += 1 } }) {
                    Image(systemName: "plus")
                        .font(.system(size: 14, weight: .semibold))
                        .foregroundColor(seatCount < 8 ? .white : .white.opacity(0.4))
                }
            }
            .frame(maxWidth: .infinity)
            .padding(.vertical, 14)
            .background(Color.gray.opacity(0.2))
            .overlay(
                RoundedRectangle(cornerRadius: 12)
                    .stroke(Color.white.opacity(0.3), lineWidth: 1)
            )
            .clipShape(RoundedRectangle(cornerRadius: 12))
        }
    }
    
    private var locationSection: some View {
        Button(action: {}) {
            HStack {
                Image(systemName: "mappin.circle.fill")
                    .font(.system(size: 20))
                    .foregroundColor(.white)
                
                VStack(alignment: .leading, spacing: 4) {
                    Text(theaterName)
                        .font(.system(size: 16, weight: .semibold))
                        .foregroundColor(.white)
                    
                    Text(theaterLocation)
                        .font(.system(size: 14))
                        .foregroundColor(.white.opacity(0.7))
                }
                
                Spacer()
                
                Image(systemName: "chevron.down")
                    .font(.system(size: 12, weight: .semibold))
                    .foregroundColor(.white.opacity(0.7))
            }
            .padding(.horizontal, 16)
            .padding(.vertical, 12)
        }
    }
    
    private var seatMapSection: some View {
        VStack(spacing: 16) {
            // Screen Representation
            VStack(spacing: 8) {
                
                Text("All eyes this way please")
                    .font(.system(size: 11))
                    .foregroundColor(.white.opacity(0.5))
            }
            .padding(.bottom, 8)
            
            // Seat Map
            VStack(spacing: 10) {
                // Column numbers header
                HStack(spacing: 4) {
                    Spacer()
                        .frame(width: 24)
                    ForEach(seatColumns, id: \.self) { col in
                        Text("\(col)")
                            .font(.system(size: 9, weight: .medium))
                            .foregroundColor(.white.opacity(0.5))
                            .frame(width: 24)
                    }
                }
                .padding(.leading, 4)
                
                // Seat rows
                ForEach(seatRows, id: \.self) { row in
                    HStack(spacing: 4) {
                        // Row label
                        Text(row)
                            .font(.system(size: 11, weight: .semibold))
                            .foregroundColor(.white.opacity(0.7))
                            .frame(width: 20)
                        
                        // Seats in row
                        ForEach(seatColumns, id: \.self) { col in
                            SeatView(
                                seatId: "\(row)\(col)",
                                isOccupied: occupiedSeats.contains("\(row)\(col)"),
                                isSelected: selectedSeats.contains("\(row)\(col)")
                            ) {
                                toggleSeat("\(row)\(col)")
                            }
                        }
                    }
                }
            }
            .padding(.vertical, 16)
            .padding(.horizontal, 8)
            .background(Color.gray.opacity(0.1))
            .clipShape(RoundedRectangle(cornerRadius: 16))
            
            // Legend
            HStack(spacing: 12) {
                LegendItem(color: Color.gray.opacity(0.3), label: "Available")
                LegendItem(color: Color.white.opacity(0.8), label: "Selected")
                LegendItem(color: Color.red.opacity(0.5), label: "Occupied")
            }
            .padding(.top, 8)
        }
    }
    
    private var showtimeSection: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Select Showtime")
                .font(.system(size: 18, weight: .semibold))
                .foregroundColor(.white)
            
            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: 12) {
                    ForEach(showtimes) { showtime in
                        ShowtimeButton(
                            showtime: showtime,
                            isSelected: selectedShowtime?.id == showtime.id
                        ) {
                            selectedShowtime = showtime
                        }
                    }
                }
                .padding(.horizontal, 4)
            }
        }
    }
    
    private var buyTicketsButton: some View {
        Button(action: {
            // Handle purchase
        }) {
            HStack(spacing: 0) {
                Text("Buy Tickets")
                    .font(.system(size: 18, weight: .semibold))
                    .foregroundColor(.black)
                    .frame(maxWidth: .infinity)
                    .padding(.vertical, 18)
                    .background(Color(red: 0.96, green: 0.94, blue: 0.88))
                
                VStack(spacing: 4) {
                    Text("$\(String(format: "%.2f", totalPrice))")
                        .font(.system(size: 18, weight: .bold))
                        .foregroundColor(.white)
                    
                    Text("\(selectedSeats.count) seat\(selectedSeats.count == 1 ? "" : "s")")
                        .font(.system(size: 11))
                        .foregroundColor(.white)
                }
                .frame(width: 100)
                .padding(.vertical, 18)
                .background(Color.gray.opacity(0.2))
            }
        }
        .disabled(selectedSeats.isEmpty || selectedShowtime == nil)
        .opacity((selectedSeats.isEmpty || selectedShowtime == nil) ? 0.5 : 1.0)
    }
    
    private func toggleSeat(_ seatId: String) {
        if occupiedSeats.contains(seatId) {
            return // Can't select occupied seats
        }
        
        if selectedSeats.contains(seatId) {
            selectedSeats.remove(seatId)
        } else {
            if selectedSeats.count < seatCount {
                selectedSeats.insert(seatId)
            }
        }
    }
    
    private var dateFormatter: DateFormatter {
        let formatter = DateFormatter()
        formatter.dateFormat = "d MMM"
        return formatter
    }
}

// MARK: - Supporting Views

struct SeatView: View {
    let seatId: String
    let isOccupied: Bool
    let isSelected: Bool
    let action: () -> Void
    
    var body: some View {
        Button(action: action) {
            VStack(spacing: 2) {
                RoundedRectangle(cornerRadius: 5)
                    .fill(seatColor)
                    .frame(width: 24, height: 24)
                    .overlay(
                        RoundedRectangle(cornerRadius: 5)
                            .stroke(strokeColor, lineWidth: isSelected ? 2 : 0)
                    )
                
                Text(seatId)
                    .font(.system(size: 7, weight: .medium))
                    .foregroundColor(.white.opacity(0.6))
            }
        }
        .disabled(isOccupied)
    }
    
    private var seatColor: Color {
        if isOccupied {
            return Color.red.opacity(0.5)
        } else if isSelected {
            return Color.white.opacity(0.8)
        } else {
            return Color.gray.opacity(0.3)
        }
    }
    
    private var strokeColor: Color {
        if isSelected {
            return Color.white
        }
        return Color.clear
    }
}

struct ShowtimeButton: View {
    let showtime: Showtime
    let isSelected: Bool
    let action: () -> Void
    
    var body: some View {
        Button(action: action) {
            Text(showtime.time)
                .font(.system(size: 15, weight: .medium))
                .foregroundColor(isSelected ? .black : .white)
                .padding(.horizontal, 20)
                .padding(.vertical, 12)
                .background(isSelected ? Color(red: 0.96, green: 0.94, blue: 0.88) : Color.gray.opacity(0.2))
                .overlay(
                    RoundedRectangle(cornerRadius: 12)
                        .stroke(isSelected ? Color.black.opacity(0.2) : Color.white.opacity(0.3), lineWidth: 1)
                )
                .clipShape(RoundedRectangle(cornerRadius: 12))
        }
    }
}

struct LegendItem: View {
    let color: Color
    let label: String
    
    var body: some View {
        HStack(spacing: 6) {
            RoundedRectangle(cornerRadius: 4)
                .fill(color)
                .frame(width: 16, height: 16)
            
            Text(label)
                .font(.system(size: 11))
                .foregroundColor(.white.opacity(0.7))
        }
    }
}

struct DatePickerSheet: View {
    @Binding var selectedDate: Date
    @Environment(\.dismiss) private var dismiss
    @Environment(\.colorScheme) var colorScheme
    
    var body: some View {
        NavigationStack {
            ZStack {
                // Light background for date picker visibility
                Color(.systemBackground)
                    .ignoresSafeArea()
                
                VStack {
                    DatePicker("Select Date", selection: $selectedDate, displayedComponents: .date)
                        .datePickerStyle(.graphical)
                        .colorScheme(.light)
                        .padding()
                }
            }
            .navigationTitle("Select Date")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    Button("Done") {
                        dismiss()
                    }
                    .foregroundColor(.blue)
                }
            }
        }
        .presentationDetents([.medium, .large])
        .presentationDragIndicator(.visible)
    }
}

// MARK: - Models

struct Showtime: Identifiable {
    let id: Int
    let time: String
    let available: Bool
}

More Templates

Similar Blogs

View All Articles
How to Translate Xcode String Catalogs with AI

How to Translate Xcode String Catalogs with AI

Learn how to efficiently translate Xcode string catalogs using AI fast, streamlining localization for your iOS and SwiftUI applications.

by dockui
May 4
How to optimize your SwiftUI app in 2026

How to optimize your SwiftUI app in 2026

Discover top strategies to optimize your SwiftUI app in 2026, enhancing performance, responsiveness, and maintainability with modern concurrency.

by dockui
Apr 29
SwiftUI: iOS Confirmation Alert (Confirm + Cancel)

SwiftUI: iOS Confirmation Alert (Confirm + Cancel)

SwiftUI iOS confirmation alert with Confirm and Cancel buttons. Beginners guide to building safe actions, destructive deletes, and commit dialogs.

by dockui
Nov 11
SwiftUI: IOS Haptic Feedback Guide 2026

SwiftUI: IOS Haptic Feedback Guide 2026

Add click to vibrate haptic feedback in SwiftUI. Full guide to UIImpactFeedbackgenerator, success, error, selection, and tap vibration patterns with code.

by dockui
Nov 11

Code copied to clipboard!