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

SwiftUI中提前执行数据获取,为何unlockDate始终显示当前日期?

问题描述

我在SwiftUI中为BoughtItemCard创建了BoughtItemViewModel,用于从Firebase获取已购物品的解锁日期,希望在BoughtItemOverlay中展示这个日期。但实际运行时,视图始终显示当前日期,而非从Firebase获取到的解锁日期。

相关代码如下:

BoughtItemCard 代码

enum SomeType {
    case song(Song)
}
struct BoughtItemCard: View {
    @State var type: SomeType
    @StateObject var vm = BoughtItemViewModel()
    
    var body: some View {
        switch type {
        case .song(let song):
            WebImage(url: URL(string: song.image))
                .resizable()
                .frame(width: 150, height: 150)
                .overlay(BoughtItemOverlay(type: type)
                .environmentObject(vm)
                .onAppear() {
                        vm.fetchUnlockDate(path: "songs", docId: song.id ?? "")
                })
        }
    }
}

BoughtItemViewModel 代码

class BoughtItemViewModel: ObservableObject {
    @Published var unlockDate = Date()
    
    func fetchUnlockDate(path: String, docId: String) {
        let uid = FirebaseManager.shared.auth.currentUser?.uid  ?? ""
        FirebaseManager.shared.firestore.collection(path)
            .document(docId).collection("unlocked").document(uid).getDocument { docSnapshot, error in
                if let error = error {
                    print(error)
                    return
                }
                let data = docSnapshot?.data()
                self.unlockDate = (data?["unlockDate"] as? Date ?? Date())
            }
    }
}

BoughtItemOverlay 代码

struct BoughtItemOverlay: View {
    @State var showModal: Bool = false
    @State var type: SomeType
    @State var unlockDate = Date()
    @EnvironmentObject var vm: BoughtItemViewModel
    
    var body: some View {
        switch type {
        case .song(let song):
            VStack {
                Spacer().onAppear() {
                    unlockDate = vm.unlockDate
                }
                Text("Purchased \(unlockDate.formatted(.dateTime.day().month(.defaultDigits).year()))")
            }
        }
    }
}
问题原因
  1. 异步数据同步时机问题:Firebase的getDocument是异步操作,当Spacer的onAppear执行时,ViewModel的unlockDate还没从Firebase获取到最新值,此时赋值的是初始的当前日期。而且onAppear只会执行一次,后续ViewModel的unlockDate更新后,本地的@State var unlockDate不会自动同步。
  2. 本地State冗余:你在BoughtItemOverlay中声明了本地的@State var unlockDate,但没有监听ViewModel中@Published的unlockDate变化,导致ViewModel数据更新时,视图无法感知并刷新。
解决办法

1. 移除本地冗余State,直接绑定ViewModel的Published属性

删除BoughtItemOverlay中的@State var unlockDate,直接使用vm.unlockDate。因为@Published属性会在值变化时自动触发视图刷新,无需手动同步。

2. 调整onAppear的位置(可选但更合理)

将onAppear移到BoughtItemCard的WebImage或整个Overlay外层,确保视图出现时正确触发数据请求,避免嵌套层级导致的执行时机问题。

修正后的代码

BoughtItemOverlay 修正版

struct BoughtItemOverlay: View {
    @State var showModal: Bool = false
    let type: SomeType // 改为let,type是传入参数无需修改
    @EnvironmentObject var vm: BoughtItemViewModel
    
    var body: some View {
        switch type {
        case .song(let song):
            VStack {
                Spacer()
                Text("Purchased \(vm.unlockDate.formatted(.dateTime.day().month(.defaultDigits).year()))")
            }
        }
    }
}

BoughtItemCard 修正版(可选优化)

struct BoughtItemCard: View {
    let type: SomeType // 改为let,参数无需用@State修饰
    @StateObject var vm = BoughtItemViewModel()
    
    var body: some View {
        switch type {
        case .song(let song):
            WebImage(url: URL(string: song.image))
                .resizable()
                .frame(width: 150, height: 150)
                .overlay(
                    BoughtItemOverlay(type: type)
                        .environmentObject(vm)
                )
                .onAppear {
                    vm.fetchUnlockDate(path: "songs", docId: song.id ?? "")
                }
        }
    }
}
额外说明
  • 使用@EnvironmentObject传递ViewModel时,确保父视图已经正确初始化并注入环境对象(这里@StateObject的使用是正确的)。
  • 如果需要避免重复调用fetchUnlockDate(比如视图重复出现时),可以在ViewModel中添加一个标记,判断是否已经获取过数据,防止重复请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 07:15:35