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

如何在代码执行过程中更新SwiftUI界面向用户反馈运行进度?

核心原因

SwiftUI的所有UI渲染操作都运行在主线程,你之前的方案之所以看不到中间进度更新,是因为耗时任务直接在主线程同步执行,阻塞了主线程的UI刷新队列。所有对Alert文本、Text内容的修改都会被暂时压入队列,等耗时任务执行完成后才会统一批量渲染,因此只能看到最终状态。

实现方案

首先你需要把耗时的计算、IO等任务移到非主线程执行,仅在需要更新进度时切回主线程修改状态变量,就能实现UI的实时刷新。

方案1:Sheet弹窗展示进度(推荐)

系统Alert的设计定位是轻量提示,不适合频繁更新内容,用Sheet自定义进度弹窗体验更好,示例代码如下:

视图模型代码

import SwiftUI

@MainActor
class ProgressViewModel: ObservableObject {
    @Published var showProgress = false
    @Published var progressText = "准备中..."
    @Published var progressValue: Double = 0.0 // 有明确进度时使用
    
    func runHeavyTask() async {
        showProgress = true
        let totalStep = 10 // 替换为你的任务总步数
        for i in 0..<totalStep {
            // 替换为你的实际业务逻辑
            try? await Task.sleep(nanoseconds: 500_000_000)
            // 因ViewModel标记了@MainActor,直接修改状态即可自动切到主线程
            progressText = "正在处理第\(i+1)项,共\(totalStep)项"
            progressValue = Double(i+1)/Double(totalStep)
        }
        progressText = "处理完成"
        try? await Task.sleep(nanoseconds: 1_000_000_000)
        showProgress = false
    }
}

视图层代码

struct ContentView: View {
    @StateObject private var vm = ProgressViewModel()
    
    var body: some View {
        Button("启动任务") {
            Task {
                await vm.runHeavyTask()
            }
        }
        .sheet(isPresented: $vm.showProgress) {
            VStack(spacing: 20) {
                ProgressView(value: vm.progressValue) {
                    Text(vm.progressText)
                } currentValueLabel: {
                    Text(String(format: "%.0f%%", vm.progressValue * 100))
                }
                .padding()
                .interactiveDismissDisabled() // 禁止用户手动关闭弹窗
            }
            .frame(width: 300, height: 150)
        }
    }
}

方案2:Alert实现动态进度(不推荐)

如果一定要用Alert实现,只要保证耗时任务在后台执行,绑定Alert的message到动态状态变量即可实时更新:

struct AlertProgressDemo: View {
    @State private var showAlert = false
    @State private var alertMessage = "准备中..."
    
    var body: some View {
        Button("启动任务") {
            showAlert = true
            // 后台执行耗时任务
            Task.detached {
                let totalStep = 10
                for i in 0..<totalStep {
                    // 替换为实际业务逻辑
                    try? await Task.sleep(nanoseconds: 500_000_000)
                    // 更新UI必须切回主线程
                    await MainActor.run {
                        alertMessage = "正在处理第\(i+1)项"
                    }
                }
                await MainActor.run {
                    alertMessage = "处理完成"
                    // 延迟1秒关闭弹窗
                    Task {
                        try? await Task.sleep(nanoseconds: 1_000_000_000)
                        showAlert = false
                    }
                }
            }
        }
        .alert("处理中", isPresented: $showAlert) {
            Button("取消", role: .cancel) {
                // 可添加任务取消逻辑
            }
        } message: {
            Text(alertMessage)
        }
    }
}

兼容老代码的GCD实现方案

如果你的项目还没有使用Swift Concurrency,可以用GCD实现同样的效果:

// 按钮点击回调
Button("启动任务") {
    showProgress = true
    // 把耗时任务丢到全局后台队列
    DispatchQueue.global().async {
        let totalStep = 10
        for i in 0..<totalStep {
            // 模拟耗时操作
            Thread.sleep(forTimeInterval: 0.5)
            // 进度更新切回主队列
            DispatchQueue.main.async {
                progressText = "正在处理第\(i+1)项"
                progressValue = Double(i+1)/Double(totalStep)
            }
        }
        DispatchQueue.main.async {
            progressText = "处理完成"
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                showProgress = false
            }
        }
    }
}
注意事项
  • 所有UI相关的状态修改必须在主线程执行,否则会出现渲染异常甚至崩溃
  • 耗时任务绝对不能放在主线程同步执行,否则一定会阻塞UI刷新
  • 可以给耗时任务添加取消逻辑,用户点击取消按钮时终止正在执行的后台任务

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 18:57:02