如何在SwiftUI中实现符合真实巴士布局的动态座位UI?
SwiftUI巴士座位UI优化实现
问题场景
需要实现模拟真实巴士的座位布局:左右两侧各设2列座位,中间保留过道,座位数据为API动态获取,但当前代码的布局无过道,座位排列不符合巴士的真实结构。
原代码与效果
原实现代码
import SwiftUI struct BusSearchView: View { let seats: [Seat] = [Seat(id: 1, displayName: "1", isBooked: true), Seat(id: 2, displayName: "2", isBooked: false), Seat(id: 3, displayName: "3", isBooked: true), Seat(id: 4, displayName: "4", isBooked: false), Seat(id: 5, displayName: "5", isBooked: false), Seat(id: 6, displayName: "6", isBooked: true), Seat(id: 7, displayName: "7", isBooked: false), Seat(id: 8, displayName: "8", isBooked: false), Seat(id: 9, displayName: "9", isBooked: false), ] var body: some View { LazyVGrid(columns: [GridItem(), GridItem() , GridItem() , GridItem()]) { ForEach(seats) { seat in Button(action: { // Handle seat selection here print(seat.displayName) }) { Text(seat.displayName) } .foregroundColor(seat.isBooked ? .gray : .black) } } } } struct Seat: Identifiable { let id: Int let displayName: String let isBooked: Bool }
原效果截图

优化方案
核心调整点
- 布局结构:采用
HStack分隔左右座位区,中间间距作为过道,每个区域用双列LazyVGrid实现每排2个座位的排列 - 座位样式:给座位添加背景、圆角和禁用状态,更贴近真实座位视觉
- 动态数据适配:通过座位ID的模运算分组,适配API返回的连续ID座位数据(若ID不连续,可根据API返回的座位位置字段调整分组逻辑)
优化后代码
import SwiftUI struct BusSearchView: View { // 模拟API动态获取的座位数据 let seats: [Seat] = [Seat(id: 1, displayName: "1", isBooked: true), Seat(id: 2, displayName: "2", isBooked: false), Seat(id: 3, displayName: "3", isBooked: true), Seat(id: 4, displayName: "4", isBooked: false), Seat(id: 5, displayName: "5", isBooked: false), Seat(id: 6, displayName: "6", isBooked: true), Seat(id: 7, displayName: "7", isBooked: false), Seat(id: 8, displayName: "8", isBooked: false), Seat(id: 9, displayName: "9", isBooked: false), Seat(id: 10, displayName: "10", isBooked: true) ] var body: some View { ScrollView { HStack(spacing: 40) { // 左侧座位区:每排2个座位 LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) { ForEach(seats.filter { seat in seat.id % 4 == 1 || seat.id % 4 == 2 }) { seat in SeatButton(seat: seat) } // 补全最后一排空位(可选,根据需求调整) if seats.filter({ $0.id % 4 == 1 || $0.id % 4 == 2 }).count % 2 != 0 { Color.clear.frame(height: 60) } } // 右侧座位区:每排2个座位 LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) { ForEach(seats.filter { seat in seat.id % 4 == 3 || seat.id % 4 == 0 }) { seat in SeatButton(seat: seat) } } } .padding() } } } // 抽离座位按钮子视图,复用样式逻辑 struct SeatButton: View { let seat: Seat var body: some View { Button(action: { print("选中座位:\(seat.displayName)") }) { Text(seat.displayName) .font(.headline) .frame(width: 60, height: 60) .background(seat.isBooked ? Color.gray.opacity(0.5) : Color.blue.opacity(0.3)) .foregroundColor(seat.isBooked ? .white : .black) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(seat.isBooked ? Color.gray : Color.blue, lineWidth: 1) ) } .disabled(seat.isBooked) } } struct Seat: Identifiable { let id: Int let displayName: String let isBooked: Bool }
优化后效果说明
- 左右两侧各2列座位,中间保留明显过道,符合真实巴士布局
- 座位有可视化样式:已预订座位为灰色半透明,可选座位为蓝色半透明,带边框区分
- 支持动态数据:只要API返回的座位ID连续,即可自动分组到对应区域;若ID不连续,可修改
filter逻辑(比如根据API返回的row和side字段分组)
内容的提问来源于stack exchange,提问作者Noob Developer
相关产品推荐
相关产品推荐

