Swift集成QuickLook预览PDF时QLPreviewItem类型不匹配报错
报错根因
两处编译错误本质是对QLPreviewController数据源方法的执行逻辑理解错误:
previewController(_:previewItemAt:)是同步返回的数据源方法,要求方法执行结束立刻返回遵守QLPreviewItem协议的对象,你把异步网络下载逻辑写在这个方法内部,方法本身没有返回任何值(默认返回空值()),所以触发Return expression of type '()' does not conform to 'QLPreviewItem'报错。- 你写在下载回调里的
return filePath! as QLPreviewItem,是给下载完成的闭包返回值,而getPayroll方法定义的completion闭包返回值是Void(空),所以触发Cannot convert value of type 'QLPreviewItem' to closure result type 'Void'报错——异步回调里的return不会给外层的数据源方法传值,逻辑完全不成立。
QuickLook 正确集成逻辑
QLPreviewController的工作逻辑是:控制器被弹出后,主动向数据源同步拉取要预览的本地文件地址,不会等待异步请求执行。因此不能把文件下载逻辑放在数据源方法里,正确流程是:
- 先触发PDF文件下载,等待文件保存到本地沙盒拿到本地URL
- 用属性存储待预览的本地URL
- 再弹出
QLPreviewController,数据源方法同步返回之前存好的URL即可
修正后代码
1. 新增存储预览文件URL的属性
在PersonalInfoDetailViewController里新增属性,用来存下载完成的PDF路径:
var payrollFileURL: URL?
2. 修正点击cell的逻辑,先下载再弹预览
把原来直接弹预览控制器的逻辑改成先下载,下载完成后再弹出:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { switch cellType(rawValue: selected.title) { case .salary: selectedPayment = payment![indexPath.row] // 先发起下载请求 RtukNetworkManager.shared.getPayroll( year: selectedPayment.year, month: selectedPayment.month, salaryType: selectedPayment.paymentType ) { [weak self] fileURL in guard let self = self, let fileURL = fileURL else { // 此处可自行添加下载失败提示,比如弹窗提示"工资单获取失败" return } // 存储下载好的本地文件路径 self.payrollFileURL = fileURL DispatchQueue.main.async { // 下载完成后再初始化弹出预览控制器 let previewQL = QLPreviewController() previewQL.dataSource = self // 单文件预览固定传0即可 previewQL.currentPreviewItemIndex = 0 self.show(previewQL, sender: nil) } } case .leave: break case .personnel: break case .none: break } }
3. 修正数据源方法,同步返回存储的URL
把数据源方法里的异步下载逻辑全部删掉,直接返回之前存好的URL即可(NSURL默认实现了QLPreviewItem协议,可直接强转):
func numberOfPreviewItems(in controller: QLPreviewController) -> Int { // 存在可预览文件返回1,否则返回0 return payrollFileURL != nil ? 1 : 0 } func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { return payrollFileURL! as NSURL }
额外优化点
- 你的
getPayroll方法存在逻辑漏洞:判断本地文件已存在时,调用完completion没有终止代码执行,会继续发起重复的网络请求,需要在文件存在的分支最后加return:
if FileManager().fileExists(atPath: destinationUrl.path) { print("File already exists [\(destinationUrl.path)]") completion(destinationUrl) return // 新增这行,终止后续下载逻辑 }
QLPreviewController仅支持预览本地沙盒内的文件,远程URL无法直接加载,必须先下载到本地再传入路径,当前你的下载逻辑是符合要求的,只是调用时机错误。- 如果需要预览多个文件,可将存储单个URL的属性改为URL数组,
numberOfPreviewItems返回数组count,previewItemAt返回对应下标的URL即可。
内容的提问来源于stack exchange,提问作者Mert Köksal
相关产品推荐
相关产品推荐

