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

SwiftUI使用fileExporter导出文件时展示ProgressView的问题求解

解决方案

核心问题

原代码存在两处逻辑错误导致需求无法实现:

  • 耗时的文档生成逻辑放在FileDocument的初始化方法中,且直接在fileExporter参数中初始化FileDocument实例,这部分逻辑会在主线程执行,直接阻塞UI导致ProgressView无法正常渲染动画
  • 用同一个状态isExporting同时控制ProgressView显示和导出弹窗弹出,不符合「先生成文档、再弹出保存弹窗」的时序要求

完整实现代码

import SwiftUI
import UniformTypeIdentifiers

struct ContentView: View {
    // 控制加载动画显示
    @State private var isGeneratingDocument = false
    // 控制导出弹窗显示
    @State private var isExporting = false
    // 存储预生成的文档对象
    @State private var generatedDocument: FileExport?

    var body: some View {
        ZStack {
            VStack {
                Button(action: {
                    startGenerateDocument()
                }, label: {
                    Text("Export File")
                })
                .padding()
            }
            // 生成文档时展示全屏加载动画
            if isGeneratingDocument {
                ProgressView("正在生成文档...")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(.ultraThinMaterial)
            }
        }
        // 导出修饰符挂载到根视图,文档生成完成后才触发弹窗
        .fileExporter(isPresented: $isExporting, document: generatedDocument, contentType: .plainText) { result in
            defer {
                generatedDocument = nil
            }
            if case .success(let url) = result {
                print(url)
                print("File Saved")
            } else {
                print("File Not Saved")
            }
        }
    }

    private func startGenerateDocument() {
        // 先显示加载动画
        isGeneratingDocument = true
        // 异步执行耗时操作,不阻塞主线程
        Task {
            // 模拟2秒耗时的文档生成逻辑,替换为实际的大文件生成代码
            try? await Task.sleep(nanoseconds: 2 * 1_000_000_000)
            // 生成文档对象
            let document = FileExport(contents: "This is a simple Text")
            // 回到主线程更新UI
            await MainActor.run {
                // 隐藏加载动画
                isGeneratingDocument = false
                // 存储生成好的文档
                generatedDocument = document
                // 弹出导出选择弹窗
                isExporting = true
            }
        }
    }
}

// FileDocument实现,仅做数据封装,无耗时逻辑
struct FileExport: FileDocument {
    static var readableContentTypes: [UTType] {[.plainText]}
    
    var contents: String
    
    init(contents: String) {
        self.contents = contents
    }
    
    init(configuration: ReadConfiguration) throws {
        guard let data = configuration.file.regularFileContents,
              let string = String(data: data, encoding: .utf8) else {
            throw CocoaError(.fileReadCorruptFile)
        }
        contents = string
    }
    
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        guard let data = contents.data(using: .utf8) else {
            throw CocoaError(.fileWriteUnknown)
        }
        return FileWrapper(regularFileWithContents: data)
    }
}

关键改动说明

  • 拆分了两个状态变量,严格区分「文档生成中」和「准备导出」两个阶段的UI展示逻辑,避免状态冲突
  • 耗时的文档生成逻辑放到异步任务中执行,不阻塞主线程,保证ProgressView可以正常渲染加载动画
  • 预先生成完整的FileDocument实例后再触发fileExporter弹窗,不会出现弹窗卡顿的问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 05:24:07