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

SwiftUI Toggle实现乐观UI时避免无限循环的解决方案咨询

解决Toggle网络请求失败无限循环的问题

核心问题

当网络请求失败后重置isOn状态时,会再次触发onChanged回调,导致无限发起网络请求。根源是没有区分用户主动触发的状态变更和代码内部的状态回滚,所有isOn的变化都会触发请求逻辑。

解决方案:使用自定义绑定区分操作来源

通过自定义Toggle的绑定逻辑,让网络请求只在用户主动操作Toggle时触发,ViewModel内部回滚状态时不会触发请求。

修改后的代码

ViewModel部分

@MainActor class ViewModel: ObservableObject {
    @Published var isOn: Bool = true

    func updatePreference(isOn: Bool) {
        Task {
            var request = URLRequest(...)
            do {
                _ = try await URLSession.shared.data(for: request)
                // 请求成功,无需额外操作,isOn已经是用户设置的值
            } catch {
                // 请求失败,回滚状态(此修改不会触发网络请求)
                self.isOn = !isOn
            }
        }
    }
}

View部分

struct ContentView: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        Toggle(isOn: Binding(
            get: { viewModel.isOn },
            set: { newValue in
                // 用户操作Toggle时,先更新状态,再发起请求
                viewModel.isOn = newValue
                viewModel.updatePreference(isOn: newValue)
            }
        )) {
            Text("偏好设置")
        }
    }
}

为什么这个方案有效?

  • Toggle的自定义绑定中,get方法仅负责同步UI状态,set方法只会在用户手动切换Toggle时被调用。
  • 当网络请求失败,ViewModel内部修改isOn回滚状态时,只会触发UI更新,不会调用set方法,因此不会再次发起网络请求,从根源避免了循环。

额外优化:添加加载状态(可选)

如果需要在请求过程中禁用Toggle或显示加载提示,可以在ViewModel中添加加载状态标记:

@MainActor class ViewModel: ObservableObject {
    @Published var isOn: Bool = true
    @Published var isLoading = false // 新增加载状态

    func updatePreference(isOn: Bool) {
        guard !isLoading else { return } // 防止重复发起请求
        isLoading = true
        
        Task {
            defer { isLoading = false } // 请求结束后重置加载状态
            var request = URLRequest(...)
            do {
                _ = try await URLSession.shared.data(for: request)
            } catch {
                self.isOn = !isOn
            }
        }
    }
}

然后在View中禁用Toggle并显示加载:

struct ContentView: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        Toggle(isOn: Binding(
            get: { viewModel.isOn },
            set: { newValue in
                viewModel.isOn = newValue
                viewModel.updatePreference(isOn: newValue)
            }
        )) {
            if viewModel.isLoading {
                ProgressView()
            } else {
                Text("偏好设置")
            }
        }
        .disabled(viewModel.isLoading)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 03:35:16