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

SwiftUI中异步下载PDF后导航至预览页的优雅实现方案

优化方案:用ObservableObject重构文档导航逻辑

你的现有实现已经能正常工作,但把状态和业务逻辑都堆在View里会让代码逐渐臃肿,也不太贴合SwiftUI的声明式设计思路。我们可以把文档的下载、状态管理抽离到一个ObservableObject的ViewModel中,让View只专注于UI展示和用户交互,代码会更优雅易维护。

第一步:创建文档管理ViewModel

这个ViewModel会统一处理文档的异步下载、状态维护,以及导航触发逻辑:

import PDFKit
import SwiftUI

enum DocState {
    case setup, ready, loading, error
}

class DocumentViewModel: ObservableObject {
    @Published var pdfDocument: PDFDocument?
    @Published var currentState: DocState = .setup
    
    func downloadAndPrepareDocument() {
        currentState = .loading
        
        // 调用你的异步下载函数
        someAsyncFunction { [weak self] doc, error in
            DispatchQueue.main.async {
                guard let self = self else { return }
                
                if let error = error {
                    // 这里可以添加错误提示,比如弹出Alert
                    print("文档下载失败:\(error.localizedDescription)")
                    self.currentState = .error
                    return
                }
                
                if let downloadedDoc = doc {
                    self.pdfDocument = downloadedDoc
                    self.currentState = .ready
                } else {
                    self.currentState = .error
                }
            }
        }
    }
}

第二步:重构List页面

在View中用@StateObject持有ViewModel,用更直观的导航API处理跳转,同时增加加载、错误状态的UI反馈:

struct ContentView: View {
    // 用@StateObject管理ViewModel生命周期,确保View存活期间ViewModel不被销毁
    @StateObject private var viewModel = DocumentViewModel()
    
    var body: some View {
        List {
            // 其他列表项...
            
            Button(action: viewModel.downloadAndPrepareDocument) {
                switch viewModel.currentState {
                case .setup, .error:
                    Text(viewModel.currentState == .error ? "重试下载" : "Show Document")
                case .loading:
                    ProgressView()
                case .ready:
                    Text("查看文档")
                }
            }
            .disabled(viewModel.currentState == .loading)
            
            // 其他列表项...
        }
        // iOS 16+推荐的导航方式,比隐藏NavigationLink更直观
        .navigationDestination(isPresented: .constant(viewModel.currentState == .ready)) {
            if let document = viewModel.pdfDocument {
                DocumentPreviewView(pdfDocument: document)
            } else {
                Text("文档加载失败")
            }
        }
        // 如果需要兼容iOS 15及以下,用下面的代码替代上面的navigationDestination
        /*
        .background(
            NavigationLink(
                destination: Group {
                    if let document = viewModel.pdfDocument {
                        DocumentPreviewView(pdfDocument: document)
                    } else {
                        Text("文档加载失败")
                    }
                },
                tag: DocState.ready,
                selection: $viewModel.currentState
            ) {
                EmptyView()
            }
        )
        */
    }
}

为什么这样更贴合SwiftUI风格?

  • 职责分离:View只负责渲染UI和响应点击,下载逻辑、状态管理都交给ViewModel,代码结构更清晰,后续扩展(比如加取消下载、重试功能)也更方便
  • 声明式状态:用@Published标记状态变化,SwiftUI会自动监听并更新UI,不需要手动同步状态和视图
  • 用户体验提升:增加了加载中、错误状态的UI反馈,用户能清楚知道当前操作的进展
  • 可测试性:ViewModel的逻辑可以单独编写单元测试,不需要依赖View层

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 03:47:41