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
相关产品推荐
相关产品推荐

