Published OnApril 1, 2025November 22, 2023

Building an airline seat map in SwiftUI

I've been asked to do a tutorial about building an airline seat map in SwiftUI many times. This has been my go-to snippet for years! It's super customizable and provides an awesome starter for any iOS developer in aviation.

If you've ever booked a flight, you're familiar with the seat map where you choose your preferred seat. In this tutorial, we'll replicate this feature using SwiftUI.

Step 1: Define the Data Model

Let's start by defining our data model:

enum SeatState {
    case empty
    case occupied
}

struct SeatId: Equatable, Hashable {
    var row: Int
    var letter: String
}

struct Seat: Identifiable, Equatable {
    var id: SeatId
    var state: SeatState
}

Here, each Seat has an id (consisting of its row and letter) and a state (which can be either empty or occupied).

Step 2: Create the Seat View

We need a visual representation of our seat:

struct SeatView: View {
    var seat: Seat

    var body: some View {
        Text(seat.id.letter)
            .frame(width: 40, height: 40)
            .background(seat.state == .empty ? Color.green : Color.red)
            .cornerRadius(8)
            .overlay(
                RoundedRectangle(cornerRadius: 8)
                    .stroke(Color.black, lineWidth: 2)
            )
    }
}

The SeatView displays the seat letter and changes its background based on its state.

Step 3: Build the Seat Map

Our goal is a grid layout with 2 seats, an aisle with a row number, 3 seats, another aisle, and then 2 more seats:

struct FirstClassView: View {

    @State var seats: [Seat] = (1..<25).map { row in
        ["A", "B", "C", "D", "E", "F", "G"].map { letter in
            Seat(id: SeatId(row: row, letter: letter), state: .empty)
        }
    }.flatMap { $0 }

    var columns: [GridItem] = [
        .init(.flexible()),  // A seat
        .init(.flexible()),  // B seat
        .init(.fixed(40)),   // Aisle with row number
        .init(.flexible()),  // C seat
        .init(.flexible()),  // D seat
        .init(.flexible()),  // E seat
        .init(.fixed(40)),   // Aisle with row number
        .init(.flexible()),  // F seat
        .init(.flexible())   // G seat
    ]

    var body: some View {
        ScrollView([.vertical, .horizontal], showsIndicators: true) {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(seats) { seat in
                    switch seat.id.letter {
                    case "A", "B", "C", "D", "E", "F", "G":
                        SeatView(seat: seat)
                            .onTapGesture {
                                toggleSeat(seat: seat)
                            }
                    default:
                        Spacer()
                    }

                    if seat.id.letter == "B" || seat.id.letter == "E" {
                        Text("\(seat.id.row)")
                            .font(.headline)
                            .frame(width: 40, height: 40)
                    }
                }
            }
            .padding()
        }
    }

    func toggleSeat(seat: Seat) {
        if let index = seats.firstIndex(of: seat) {
            seats[index].state = seats[index].state == .empty ? .occupied : .empty
        }
    }
}

With SwiftUI, building complex layouts becomes a breeze. Our seat map, with different configurations and aisles, is just a demonstration of how adaptable and flexible SwiftUI grids can be.

Remember, you can further customize the appearance, add more features like zooming, selecting multiple seats, or fetching seat availability from a server. The possibilities are limitless! Happy coding!

Read more
Announcement
Product
July 7, 2025
Ditto Acquires KeySquare Labs
by
Adam Fish
Eric Hanft
Expanding Tactical Radio Support and our Public Sector Business
When centralization becomes a liability
Product
June 30, 2025
When Centralization Becomes a Liability
by
Shiri Friedman
What 19th-century grain policies can teach us about modern app architecture