如何使用UIPrintInteractionController异步打印URL中的PDF文件?
解决iOS打印PDF时主线程阻塞导致UI冻结的问题
问题背景
iOS应用中实现打印送货单功能:点击选项后从服务器获取PDF文件URL,跳转至含打印按钮的页面。前序流程正常,但点击打印按钮后视图冻结,收到如下警告:
主线程上的同步URL加载会导致UI无响应,请改用URLSession等异步API。
现有代码直接将远程URL传入打印控制器,导致同步加载阻塞主线程。
解决方案
核心思路:先通过URLSession异步将PDF文件下载到本地临时目录,再使用本地文件URL进行打印操作,避免主线程阻塞。
1. 新增异步下载PDF的工具函数
import Foundation func downloadPDF(from url: URL, completion: @escaping (URL?, Error?) -> Void) { let task = URLSession.shared.downloadTask(with: url) { localURL, _, error in guard let localURL = localURL, error == nil else { completion(nil, error) return } // 将临时文件移到稳定的临时目录,避免被系统提前清理 let fileManager = FileManager.default let tempDir = fileManager.temporaryDirectory let destinationURL = tempDir.appendingPathComponent("\(UUID().uuidString).pdf") do { if fileManager.fileExists(atPath: destinationURL.path) { try fileManager.removeItem(at: destinationURL) } try fileManager.moveItem(at: localURL, to: destinationURL) completion(destinationURL, nil) } catch { completion(nil, error) } } task.resume() }
2. 修改打印函数,适配本地文件URL
public enum PrintingResult { case success case failure(Error) case userCancelled } public func presentPrintInteractionController(localFileURL: URL?, jobName: String? = nil, completion: ((PrintingResult) -> Void)? = nil) { let printController = UIPrintInteractionController() let printInfo = UIPrintInfo.printInfo() if let jobName = jobName { printInfo.jobName = jobName } printController.printInfo = printInfo if let localFileURL = localFileURL { printController.printingItem = localFileURL } printController.present(animated: true) { _, completed, error in guard let completion = completion else { return } if completed { completion(.success) } else { if let error = error { completion(.failure(error)) } else { completion(.userCancelled) } } } }
3. 更新SwiftUI视图,处理异步流程与加载状态
import SwiftUI struct ReportView: View { var reportUrl: String @State private var isLoading = false var body: some View { VStack { Button { guard let url = URL(string: reportUrl), !isLoading else { return } isLoading = true downloadPDF(from: url) { localURL, error in DispatchQueue.main.async { isLoading = false if let localURL = localURL { presentPrintInteractionController(localFileURL: localURL, jobName: "Print a pdf report") { result in switch result { case .success: print("打印成功") case .failure(let error): print("错误:\(error)") case .userCancelled: print("打印任务已取消") } } } else if let error = error { print("下载PDF失败:\(error)") } } } } label: { if isLoading { ProgressView() } else { Text("打印") } } .disabled(isLoading) } } } struct ReportView_Previews: PreviewProvider { static var previews: some View { ReportView(reportUrl: "") } }
关键说明
- 下载操作通过
URLSession.downloadTask在后台线程执行,完全不阻塞主线程 - 下载完成后将文件移至应用临时目录,确保打印过程中文件不会被系统清理
- 视图中添加加载状态与按钮禁用逻辑,避免用户重复触发操作
- 所有UI更新操作切回主线程执行,符合SwiftUI的线程安全要求
内容的提问来源于stack exchange,提问作者Shawkath Srijon
相关产品推荐
相关产品推荐

