如何在函数内部控制SwiftUI中@State属性的值?
问题解决:SwiftUI中异步更新状态并显示加载指示器
错误原因分析
你遇到的Cannot use mutating member on immutable value: 'self' is immutable错误,本质是因为ProgressExample是值类型(struct),mutating函数会尝试修改self,但在Button的action闭包中,self是不可变的(闭包捕获的是值的副本),所以无法调用带mutating的方法。
另外,你的updateNumbers是耗时操作,直接在主线程调用会卡住UI,导致ProgressView根本显示不出来——主线程被阻塞时,SwiftUI无法刷新视图。
修正方案
用异步任务处理耗时操作,在后台执行计算,完成后回到主线程更新状态:
修正后的代码(iOS 15+/macOS 12+)
import SwiftUI import Charts import PlaygroundSupport struct ProgressExample: View { @State private var isUpdating: Bool = false @State private var numbers: [Int] = [] var body: some View { VStack { Button("Update data") { isUpdating = true // 开启异步任务,避免阻塞主线程 Task { // 在后台线程执行耗时计算 let newNumbers = await updateNumbers() // 回到主线程更新UI状态 await MainActor.run { numbers = newNumbers isUpdating = false } } } .padding() Spacer() if isUpdating { ProgressView("Updating") } else { List { ForEach(numbers, id: \.self) { number in Text("Number: \(number)") } } } } } // 耗时函数改为异步形式,适配Swift并发模型 func updateNumbers() async -> [Int] { var randomIntegers: [Int] = [] for _ in 0...10 { randomIntegers.append(Int.random(in: 1...100)) // 用异步sleep模拟耗时,不会阻塞线程 try? await Task.sleep(nanoseconds: 100_000_000) } return randomIntegers } } PlaygroundPage.current.setLiveView(ProgressExample())
关键改动说明
- 移除
updateNumbers的mutating修饰符:状态更新统一放到UI线程的闭包中处理,不需要函数内部修改self - 用
Task包装异步操作:将耗时计算放到后台线程,主线程不会被阻塞,ProgressView能正常显示 - 用
await MainActor.run更新状态:SwiftUI的@State必须在主线程修改,确保状态更新安全 - 替换
Thread.sleep为Task.sleep:后者是异步的,不会阻塞整个线程,更符合现代Swift并发规范
兼容旧系统方案(iOS <15)
如果需要适配iOS 14及以下版本,可以用GCD实现异步处理:
Button("Update data") { isUpdating = true // 后台线程执行耗时操作 DispatchQueue.global().async { let newNumbers = self.updateNumbers() // 回到主线程更新UI DispatchQueue.main.async { self.numbers = newNumbers self.isUpdating = false } } } // 耗时函数保持同步,在后台线程调用 func updateNumbers() -> [Int] { var randomIntegers: [Int] = [] for _ in 0...10 { randomIntegers.append(Int.random(in: 1...100)) Thread.sleep(forTimeInterval: 0.1) } return randomIntegers }
内容的提问来源于stack exchange,提问作者Konrad
相关产品推荐
相关产品推荐

