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

SwiftUI中如何在Firebase的Map数组内为指定日期添加时间?

在SwiftUI中向Firebase数组内对象的指定字段添加元素的正确实现方法

问题背景

Firebase中存储的数据结构如下:

"booking_days": [
    {
      "date": "__Timestamp__2023-11-05T05:11:54.301Z",
      "day_off": false,
      "time": [
        "11:30"
      ]
    },
    {
      "date": "__Timestamp__2023-11-04T05:20:17.640Z",
      "day_off": false,
      "time": [
        "14:30"
      ]
    }
]

需求:当请求日期与booking_days数组中某个元素的date匹配时,向该元素的time数组添加新时间。预期结果如下:

"booking_days": [
    {
      "date": "__Timestamp__2023-11-05T05:11:54.301Z",
      "day_off": false,
      "time": [
        "11:30",
        "12:30"
      ]
    },
    {
      "date": "__Timestamp__2023-11-04T05:20:17.640Z",
      "day_off": false,
      "time": [
        "14:30"
      ]
    }
]

尝试的代码无法实现预期效果:

func setBookingTimeForExistingDay(userId: String, bookingDays: BookingDay, existingDay: Date) async throws {
    guard let bookingDay = try? encoder.encode(bookingDays) else {
        throw URLError(.badURL)
    }
    let orderBookingTime: [String : Any] = [DBPortfolioModel.CodingKeys.bookingDays.rawValue : FieldValue.arrayUnion([bookingDay])]

    try await portfolioUserDocument(userId: userId).setData(orderBookingTime, mergeFields: ["\(existingDay)"])
}

问题分析

原代码存在两个核心错误:

  • 使用FieldValue.arrayUnion([bookingDay])会尝试将整个BookingDay对象添加到booking_days数组中,而非修改已有对象的time数组
  • mergeFields参数传入"\(existingDay)"完全无效,该参数需指定要合并的字段路径,而非日期字符串

正确实现方案

Firebase无法直接通过条件匹配更新数组内的嵌套对象,需先定位到目标元素,再更新其内部字段。以下提供两种可行方案:

方案一:直接通过索引路径更新

先查询文档找到目标日期在数组中的索引,再通过精确路径更新time数组:

func addBookingTimeToExistingDay(userId: String, targetDate: Date, newTime: String) async throws {
    // 获取用户文档引用
    let docRef = portfolioUserDocument(userId: userId)
    let snapshot = try await docRef.getDocument()
    
    // 提取booking_days数组数据
    guard var bookingDays = snapshot.data()?[DBPortfolioModel.CodingKeys.bookingDays.rawValue] as? [[String: Any]] else {
        throw NSError(domain: "FirebaseError", code: -1, userInfo: [NSLocalizedDescriptionKey: "未找到booking_days数据"])
    }
    
    // 解析日期并找到匹配的元素索引
    let dateFormatter = ISO8601DateFormatter()
    dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
    let calendar = Calendar.current
    
    var targetIndex: Int?
    for (index, day) in bookingDays.enumerated() {
        guard let dateString = day["date"] as? String,
              let cleanDateString = dateString.components(separatedBy: "__Timestamp__").last,
              let dayDate = dateFormatter.date(from: cleanDateString) else {
            continue
        }
        // 比较日期(忽略时间,仅匹配同一天)
        if calendar.isDate(dayDate, inSameDayAs: targetDate) {
            targetIndex = index
            break
        }
    }
    
    guard let index = targetIndex else {
        throw NSError(domain: "FirebaseError", code: -2, userInfo: [NSLocalizedDescriptionKey: "未找到匹配的日期"])
    }
    
    // 构建更新路径,添加新时间(自动去重)
    let updatePath = "\(DBPortfolioModel.CodingKeys.bookingDays.rawValue)[\(index)].time"
    let updates: [String: Any] = [
        updatePath: FieldValue.arrayUnion([newTime])
    ]
    
    // 执行更新
    try await docRef.updateData(updates)
}

方案二:使用自定义模型处理

通过解码为BookingDay模型修改后再编码写回,代码可读性更高:

// 自定义BookingDay模型,适配Firebase的日期格式
struct BookingDay: Codable {
    var date: Date
    var day_off: Bool
    var time: [String]
    
    enum CodingKeys: String, CodingKey {
        case date, day_off, time
    }
    
    // 解码处理__Timestamp__前缀
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let dateString = try container.decode(String.self, forKey: .date)
        let cleanDateString = dateString.replacingOccurrences(of: "__Timestamp__", with: "")
        
        let dateFormatter = ISO8601DateFormatter()
        dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        guard let date = dateFormatter.date(from: cleanDateString) else {
            throw DecodingError.dataCorruptedError(forKey: .date, in: container, debugDescription: "日期格式无效")
        }
        
        self.date = date
        self.day_off = try container.decode(Bool.self, forKey: .day_off)
        self.time = try container.decode([String].self, forKey: .time)
    }
    
    // 编码添加__Timestamp__前缀
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        let dateFormatter = ISO8601DateFormatter()
        dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        let dateString = "__Timestamp__\(dateFormatter.string(from: date))"
        
        try container.encode(dateString, forKey: .date)
        try container.encode(day_off, forKey: .day_off)
        try container.encode(time, forKey: .time)
    }
}

// 更新方法
func addBookingTimeToExistingDay(userId: String, targetDate: Date, newTime: String) async throws {
    let docRef = portfolioUserDocument(userId: userId)
    let snapshot = try await docRef.getDocument()
    
    guard let data = snapshot.data(),
          let bookingDaysData = data[DBPortfolioModel.CodingKeys.bookingDays.rawValue] as? [[String: Any]] else {
        throw NSError(domain: "FirebaseError", code: -1, userInfo: [NSLocalizedDescriptionKey: "未找到booking_days数据"])
    }
    
    // 解码为模型数组
    var bookingDays: [BookingDay] = try bookingDaysData.map {
        let jsonData = try JSONSerialization.data(withJSONObject: $0)
        return try JSONDecoder().decode(BookingDay.self, from: jsonData)
    }
    
    // 找到目标日期并添加时间
    let calendar = Calendar.current
    if let index = bookingDays.firstIndex(where: { calendar.isDate($0.date, inSameDayAs: targetDate) }) {
        // 可选:去重避免重复时间
        if !bookingDays[index].time.contains(newTime) {
            bookingDays[index].time.append(newTime)
        }
        
        // 编码回字典数组
        let updatedBookingDays: [[String: Any]] = try bookingDays.map {
            let jsonData = try JSONEncoder().encode($0)
            return try JSONSerialization.jsonObject(with: jsonData) as! [String: Any]
        }
        
        // 更新文档
        try await docRef.updateData([
            DBPortfolioModel.CodingKeys.bookingDays.rawValue: updatedBookingDays
        ])
    } else {
        throw NSError(domain: "FirebaseError", code: -2, userInfo: [NSLocalizedDescriptionKey: "未找到匹配的日期"])
    }
}

注意事项

  • 日期比较时建议使用Calendar.current.isDate(_:inSameDayAs:),避免时区或时间戳精度导致的匹配失败
  • 使用FieldValue.arrayUnion会自动忽略重复的时间值,无需额外去重(方案一);方案二中可手动判断避免重复
  • 确保portfolioUserDocument(userId:)方法返回正确的Firestore文档引用

内容的提问来源于stack exchange,提问作者Dmitry

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 10:17:34