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

SwiftUI中如何更新Toggle开关并将其状态同步到Firestore?

SwiftUI Toggle 与 Firestore 收藏状态同步方案

问题核心

当前依赖UserDefaults存储收藏状态,无法和Firestore实时同步,导致Toggle按钮状态在多设备切换、App重启后可能出现不一致。需要将收藏状态的数据源切换为Firestore实时监听,同时保留本地缓存优化体验。

改造后的ViewModel实现

import FirebaseFirestore
import FirebaseFirestoreSwift

class FavoriteViewModel: ObservableObject {
    @Published var favoriteItem: Set<String> = []
    private let db = Firestore.firestore()
    private var listenerRegistration: ListenerRegistration?
    private let userDefaultsKey = "favoriteItem"
    
    init() {
        // 先加载本地缓存作为初始值,避免网络加载时UI空白
        loadLocalFavorites()
        // 启动Firestore数据监听,实时同步收藏状态
        startFirestoreListener()
    }
    
    deinit {
        // 销毁监听,防止内存泄漏
        listenerRegistration?.remove()
    }
    
    private func loadLocalFavorites() {
        if let storedFavorites = UserDefaults.standard.array(forKey: userDefaultsKey) as? [String] {
            favoriteItem = Set(storedFavorites)
        }
    }
    
    private func startFirestoreListener() {
        // 替换为你的用户身份标识逻辑,比如当前登录用户ID
        guard let userId = Auth.auth().currentUser?.uid else { return }
        
        listenerRegistration = db.collection("users").document(userId).collection("favorites")
            .addSnapshotListener { [weak self] querySnapshot, error in
                guard let documents = querySnapshot?.documents else {
                    print("获取收藏数据失败: \(error?.localizedDescription ?? "未知错误")")
                    return
                }
                
                // 从Firestore解析收藏项ID
                let favoriteIds = documents.compactMap { $0.documentID }
                self?.favoriteItem = Set(favoriteIds)
                // 更新本地缓存
                UserDefaults.standard.set(favoriteIds, forKey: self?.userDefaultsKey ?? "")
            }
    }
    
    func isUserFavorite(itemId: String) -> Bool {
        favoriteItem.contains(itemId)
    }
    
    func toggleFavorite(itemId: String) {
        guard let userId = Auth.auth().currentUser?.uid else { return }
        
        let favoriteRef = db.collection("users").document(userId).collection("favorites").document(itemId)
        
        if isUserFavorite(itemId: itemId) {
            // 移除收藏
            favoriteRef.delete { [weak self] error in
                if let error = error {
                    print("移除收藏失败: \(error.localizedDescription)")
                    // 操作失败时回滚本地状态,保证UI一致性
                    self?.favoriteItem.insert(itemId)
                }
            }
        } else {
            // 添加收藏,可按需存储额外字段(如添加时间)
            favoriteRef.setData(["addedAt": Timestamp()]) { [weak self] error in
                if let error = error {
                    print("添加收藏失败: \(error.localizedDescription)")
                    // 操作失败时回滚本地状态
                    self?.favoriteItem.remove(itemId)
                }
            }
        }
    }
}

View层调整

无需大幅修改,@Published变量会自动触发UI更新,保证Toggle图标实时同步:

private var favoriteButton: some View {
    Button {
        viewModel.toggleFavorite(itemId: itemId)
    } label: {
        Image(systemName: viewModel.isUserFavorite(itemId: itemId) ? "bookmark.fill" : "bookmark")
            .foregroundColor(.orange) // 可选:用颜色强化状态区分
    }
}

关键优化点

  • 实时同步:通过Firestore的addSnapshotListener实现跨设备、跨会话的收藏状态实时同步
  • 本地缓存兜底:初始化先加载UserDefaults缓存,避免网络加载时UI无响应
  • 错误回滚:Firestore操作失败时回滚本地状态,保证UI与实际数据一致
  • 内存管理:ViewModel销毁时移除监听,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 07:50:15