SwiftUI中为TripChain行程段关联多选交通方式的实现问题
解决方案
1. 调整数据模型
首先确保行程段模型支持多交通方式存储,并适配Firebase的编解码逻辑:
// 自定义交通方式枚举,支持Codable和遍历所有选项 enum Modes: String, Codable, CaseIterable { case walk, bike, bus, subway, car, taxi // 可选:添加友好显示名称 var displayName: String { switch self { case .walk: return "步行" case .bike: return "骑行" case .bus: return "公交" case .subway: return "地铁" case .car: return "自驾" case .taxi: return "打车" } } } // 行程段模型,替换单个交通方式为数组 struct TripSegment: Codable, Identifiable { let id: String let startPoint: String let endPoint: String var selectedModes: [Modes] enum CodingKeys: String, CodingKey { case id, startPoint, endPoint, selectedModes } // 编码:将枚举数组转为字符串数组存储到Firebase func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(startPoint, forKey: .startPoint) try container.encode(endPoint, forKey: .endPoint) try container.encode(selectedModes.map { $0.rawValue }, forKey: .selectedModes) } // 解码:将Firebase的字符串数组转回枚举数组 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) startPoint = try container.decode(String.self, forKey: .startPoint) endPoint = try container.decode(String.self, forKey: .endPoint) let modeStrings = try container.decode([String].self, forKey: .selectedModes) selectedModes = modeStrings.compactMap { Modes(rawValue: $0) } } // 便捷初始化器 init(startPoint: String, endPoint: String) { self.id = UUID().uuidString self.startPoint = startPoint self.endPoint = endPoint self.selectedModes = [] } }
2. 更新ViewModel逻辑
在ViewModel中维护每个行程段的交通方式选中状态,处理选择切换和Firebase存储:
class TripChainViewModel: ObservableObject { @Published var tripSegments: [TripSegment] = [] // 用字典存储每个行程段的选中状态,key为行程段ID @Published var segmentModeSelections: [String: Set<Modes>] = [:] // 添加新行程段时初始化选中状态 func addNewSegment(start: String, end: String) { let newSegment = TripSegment(startPoint: start, endPoint: end) tripSegments.append(newSegment) segmentModeSelections[newSegment.id] = [] } // 切换交通方式选中状态,并同步更新模型 func toggleMode(for segmentId: String, mode: Modes) { guard var selections = segmentModeSelections[segmentId] else { return } selections.contains(mode) ? selections.remove(mode) : selections.insert(mode) segmentModeSelections[segmentId] = selections if let index = tripSegments.firstIndex(where: { $0.id == segmentId }) { tripSegments[index].selectedModes = Array(selections) } } // 保存行程链到Firebase func saveTripChainToFirebase() async throws { let db = Firestore.firestore() // 若需将多个行程段包装为一个TripChain对象,需额外定义TripChain模型 try await db.collection("tripChains").addDocument(from: tripSegments) } }
3. 重构DiaryView的多选UI
为每个行程段添加交通方式多选控件:
struct DiaryView: View { @StateObject private var viewModel = TripChainViewModel() @State private var newStart = "" @State private var newEnd = "" var body: some View { NavigationStack { List { // 添加新行程段的输入栏 HStack { TextField("起点", text: $newStart) TextField("终点", text: $newEnd) Button("添加") { guard !newStart.isEmpty, !newEnd.isEmpty else { return } viewModel.addNewSegment(start: newStart, end: newEnd) newStart = "" newEnd = "" } } // 遍历所有行程段,展示交通方式多选 ForEach(viewModel.tripSegments) { segment in Section(header: Text("\(segment.startPoint) → \(segment.endPoint)")) { ForEach(Modes.allCases, id: \.self) { mode in HStack { Text(mode.displayName) Spacer() Toggle("", isOn: Binding( get: { viewModel.segmentModeSelections[segment.id]?.contains(mode) ?? false }, set: { _ in viewModel.toggleMode(for: segment.id, mode: mode) } )) } } } } } .navigationTitle("行程链记录") .toolbar { Button("保存") { Task { do { try await viewModel.saveTripChainToFirebase() // 保存成功后的提示逻辑 } catch { print("保存失败:\(error.localizedDescription)") } } } } } } }
核心注意事项
- 枚举必须遵循
Codable和CaseIterable,前者适配Firebase存储,后者方便遍历所有交通方式选项。 - 用
segmentModeSelections字典维护选中状态,避免直接修改tripSegments导致UI频繁刷新,同时确保模型数据同步更新。 - Firebase不支持直接存储枚举类型,需通过自定义编解码将枚举转为字符串数组存储。
内容的提问来源于stack exchange,提问作者Christian Riccio
相关产品推荐
相关产品推荐

