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

如何在函数内部控制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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 20:57:39