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

SwiftUI中ProfileView的ProgressView因Firebase数据拉取时序问题无限加载

问题根因

  • 调用时序错误:ViewModel初始化时就触发fetchData,此时sessionStore还未通过onAppear里的setup方法赋值,首次拉取直接失败。后续sessionStore赋值完成后没有重新触发拉取逻辑,导致fetching一直为true,出现无限加载。
  • 视图模型生命周期管理错误:在View内部初始化的ObservableObject使用了@ObservedObject修饰,SwiftUI 视图重绘时可能意外重建视图模型,丢失已有状态。
  • 调试代码顺序错误:fetchData中打印fetching的代码在修改fetching的逻辑之前,控制台输出的是修改前的旧值,干扰调试判断。
  • 强制解包风险:多处使用!强制解包可选值,当sessionStore或currentUser状态发生变化时,极易触发nil崩溃。
  • 回调线程风险:Firebase Storage 下载地址的闭包回调可能在后台线程执行,直接修改UI相关的@Published属性可能导致更新异常。

修复方案

1. 修改ProfileViewModel

@MainActor
class ProfileViewModel: ObservableObject {
    @Published var sessionStore: SessionStore?
    private let firestoreManager = FirestoreManager()
    private let firebaseStorageManager = FirebaseStorageManager()
    // 新增:避免重复拉取
    private var isFetching = false
    
    @Published var profile: Profile?
    @Published var profilePicturePhotoURL: URL?
    
    @Published var workouts: [IntervalWorkout]?
    @Published var fetching: Bool = true
    
    init(forPreviews: Bool) {
        [...]
    }
    
    init() {
        [...]
        // 移除init里自动调用fetchData的逻辑
    }
    
    func setup(sessionStore: SessionStore) {
        self.sessionStore = sessionStore
        // 赋值完成后主动触发拉取
        Task {
            try? await fetchData()
        }
    }
    
    func fetchData() async throws {
        // 避免重复拉取
        guard !isFetching else { return }
        isFetching = true
        defer { isFetching = false }
        
        // 替换所有强制解包为可选绑定
        guard let sessionStore = sessionStore, let currentUser = sessionStore.currentUser else {
            print("Data not fetched - no sessionStore or currentUser")
            return
        }
        
        print("Fetching Data")
        let (firstname, username, birthDate, age, country, language, gender, email, profilePictureURL) = try await self.firestoreManager.fetchDataForProfileViewModel(userID: currentUser.uid)
        
        self.profile = Profile(id: currentUser.uid, firstName: firstname, username: username, birthDate: birthDate, age: age, country: country, language: language, gender: gender, email: email, profilePictureURL: profilePictureURL)
        
        print(self.profile?.firstName ?? "No first name")
        
        if let profilePictureURL = profilePictureURL {
            self.firebaseStorageManager.getDownloadURLForImage(stringURL: profilePictureURL, userID: currentUser.uid) { [weak self] photoURL in
                // 确保在主线程更新UI属性
                DispatchQueue.main.async {
                    self?.profilePicturePhotoURL = photoURL
                    print("Data fetched")
                }
            }
        } else {
            print("Data fetched - no profilePicture")
        }
        
        if self.profile != nil {
            fetching = false
        }
        // 移到赋值后打印
        print("FETCHING: \(self.fetching)")
    }
    
    // 其他方法保持不变
    [...]
}

2. 修改ProfileView

struct ProfileView: View {
    // 替换@ObservedObject为@StateObject,正确持有视图模型
    @StateObject private var profileViewModel = ProfileViewModel()
    @EnvironmentObject private var sessionStore: SessionStore
    @Environment(\.colorScheme) var colorScheme
    
    // 其他属性保持不变
    [...]
    
    // 其他代码保持不变
    [...]
}

额外优化建议

如果需要支持用户登录状态切换后自动刷新数据,可以在setup方法中通过Combine监听sessionStore.currentUser的变化,用户登录后自动触发重新拉取。同时建议给fetchData添加错误处理逻辑,拉取失败时给用户提示,避免一直停留在加载状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 02:15:02