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

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的工作逻辑是:控制器被弹出后,主动向数据源同步拉取要预览的本地文件地址,不会等待异步请求执行。因此不能把文件下载逻辑放在数据源方法里,正确流程是:

  1. 先触发PDF文件下载,等待文件保存到本地沙盒拿到本地URL
  2. 用属性存储待预览的本地URL
  3. 再弹出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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 17:06:27