SwiftUI中加载指示器引发无限渲染问题求助
解决独立跟踪多API加载状态的UI更新与无限循环问题
问题根源拆解
- 嵌套ObservableObject的通知失效:父类
ViewModelObject里的@Published var loading仅会在LoadingIndicatorVM实例被替换时触发更新,LoadingIndicatorVM内部loadingStates的变化无法传递到父类的objectWillChange,导致UI收不到状态变更通知。 - 无限循环的触发点:
isLoading(for:)方法中每次返回false时都会修改@Published var indicatorId,这会触发视图重新渲染,而渲染过程中又会再次调用isLoading(for:),形成无限循环。
分步解决方案
第一步:移除导致无限循环的冗余代码
直接删除indicatorId相关逻辑,它不仅无实际作用,还会引发无限渲染:
class LoadingIndicatorVM<T: Hashable>: ObservableObject { @Published var loadingStates: [T: Bool] = [:] internal func updateLoadingState(for apiDictionary: T, isLoading: Bool) { loadingStates[apiDictionary] = isLoading } func isLoading(for apiDictionary: T) -> Bool { loadingStates[apiDictionary] ?? false } func isLoading() -> Bool { loadingStates.values.contains(true) } }
第二步:解决嵌套ObservableObject的UI更新问题
方案A:兼容旧版本(iOS 16及以下)—— 手动转发变更通知
在父类ViewModelObject中订阅loading的objectWillChange,将内部变化转发给父类的变更通知:
class ViewModelObject<T: Hashable, DataType>: ObservableObject { var cancellable: AnyCancellable? @Published var loading = LoadingIndicatorVM<T>() @Published var data: [DataType] = [] let manager = NetworkManager() init() { // 订阅loading的内部变更,同步触发父类的UI更新通知 cancellable = loading.objectWillChange .sink { [weak self] _ in self?.objectWillChange.send() } } }
这样LoadingIndicatorVM内部的loadingStates变化时,会触发父类的变更通知,UI就能同步更新状态。
方案B:使用新@Observable宏(iOS 17+/macOS 14+)—— 自动嵌套通知
将两个类都迁移到@Observable宏,嵌套的Observable对象内部变化会自动向上传递通知,无需手动处理:
改造LoadingIndicatorVM:
@Observable class LoadingIndicatorVM<T: Hashable> { var loadingStates: [T: Bool] = [:] internal func updateLoadingState(for apiDictionary: T, isLoading: Bool) { loadingStates[apiDictionary] = isLoading } func isLoading(for apiDictionary: T) -> Bool { loadingStates[apiDictionary] ?? false } func isLoading() -> Bool { loadingStates.values.contains(true) } }
改造父类ViewModelObject:
@Observable class ViewModelObject<T: Hashable, DataType> { var loading = LoadingIndicatorVM<T>() var data: [DataType] = [] let manager = NetworkManager() // 无需cancellable,@Observable自动处理嵌套对象的变更传递 }
这种方式更简洁,利用SwiftUI新的观察系统自动完成状态通知的传递。
第三步:视图中正确使用加载状态
在视图中直接调用viewModel.loading.isLoading(for: apiKey)即可,示例代码:
struct ContentView: View { @State private var viewModel = ViewModelObject<APIKey, DataModel>() var body: some View { Button("触发API") { viewModel.loading.updateLoadingState(for: .userList, isLoading: true) // 模拟API请求延迟 DispatchQueue.main.asyncAfter(deadline: .now() + 2) { viewModel.loading.updateLoadingState(for: .userList, isLoading: false) } } if viewModel.loading.isLoading(for: .userList) { ProgressView() } } } // 示例API标识枚举 enum APIKey: Hashable { case userList case detailInfo }
内容的提问来源于stack exchange,提问作者Mohamed Said
相关产品推荐
相关产品推荐

