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
相关产品推荐
相关产品推荐

