You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 07:13:19