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

SwiftUI Task阻塞UI:异步渲染视图生成PDF并显示进度

问题分析

你的代码中renderView被标记为@MainActor,导致整个PDF生成逻辑(包括循环渲染视图)都在主线程执行。当渲染复杂视图时,主线程被长时间占用,无法更新UI,因此ProgressView始终无法正常显示。

解决方案

将耗时的PDF生成逻辑移到后台线程执行,仅在需要渲染SwiftUI视图时切换回主线程(因为SwiftUI视图渲染必须依赖主线程)。具体修改如下:

struct ContentView: View {
    
    let array = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"]
    
    @State var hideProgressView = true
    @State var pdfURL: URL?
    
    var body: some View {
        VStack {
            if !hideProgressView {
                ProgressView()
                    .progressViewStyle(.circular)
                    .padding(.bottom)
            }
            Button {
                hideProgressView = false
                // 启动后台任务,避免阻塞主线程
                Task.detached {
                    await renderView()
                    // 回到主线程更新UI状态
                    await MainActor.run {
                        hideProgressView = true
                    }
                }
            } label: {
                Text("Generate PDF")
            }
            .buttonStyle(.borderedProminent)
            // 展示PDF的Sheet
            .sheet(item: $pdfURL) { url in
                PDFViewer(url: url)
            }
        }
    }
    
    // 移除@MainActor,让函数在后台线程执行
    func renderView() async {
        let url = URL.documentsDirectory.appending(path: "output.pdf")
        var box = CGRect(x: 0, y: 0, width: 200, height: 200)
        guard let pdf = CGContext(url as CFURL, mediaBox: &box, nil) else {
            return
        }
        
        for item in array {
            pdf.beginPDFPage(nil)
            
            // 切换回主线程渲染SwiftUI视图
            await MainActor.run {
                let renderer = ImageRenderer(content: Text(item))
                renderer.render { size, context in
                    context(pdf)
                }
            }
            
            pdf.endPDFPage()
        }
        
        pdf.closePDF()
        
        // 回到主线程更新pdfURL状态
        await MainActor.run {
            pdfURL = url
        }
    }
}

// PDF预览视图
struct PDFViewer: View {
    let url: URL
    
    var body: some View {
        WebView(url: url)
    }
}

// UIKit WebView包装,用于预览PDF
struct WebView: UIViewRepresentable {
    let url: URL
    
    func makeUIView(context: Context) -> WKWebView {
        WKWebView()
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        uiView.load(URLRequest(url: url))
    }
}
关键说明
  • Task.detached:将PDF生成任务放到后台线程执行,主线程可以正常响应用户交互、更新ProgressView。
  • MainActor.run:SwiftUI视图渲染必须在主线程完成,通过这个方法临时切换回主线程执行视图渲染,完成后立即回到后台继续处理PDF逻辑,避免长时间阻塞主线程。
  • 状态更新:所有修改@State变量的操作必须在主线程执行,因此更新hideProgressView和pdfURL时,都通过MainActor.run确保在主线程执行。

这样修改后,即使渲染复杂视图,ProgressView也能正常显示,主UI不会被阻塞,PDF生成完成后会自动弹出Sheet展示内容。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 04:57:30