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

SwiftUI中加载指示器引发无限渲染问题求助

解决独立跟踪多API加载状态的UI更新与无限循环问题

问题根源拆解

  1. 嵌套ObservableObject的通知失效:父类ViewModelObject里的@Published var loading仅会在LoadingIndicatorVM实例被替换时触发更新,LoadingIndicatorVM内部loadingStates的变化无法传递到父类的objectWillChange,导致UI收不到状态变更通知。
  2. 无限循环的触发点: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 14:35:10