WKWebView中PDF下载失效 下载代理方法未触发问题
WKWebView 无法触发PDF下载回调、报WebKit错误102的问题
问题描述
使用WKWebView加载网页时,站点内点击按钮生成PDF并触发下载,已实现导航、下载相关代理方法,但运行时仅webView(_:decidePolicyFor:preferences:decisionHandler:)方法被调用,且navigationAction.shouldPerformDownload始终为false,其余下载相关代理均不触发,控制台输出错误:
WebPageProxy::didFailProvisionalLoadForFrame: frameID=3, domain=WebKitErrorDomain, code=102, isMainFrame=1
已实现的核心代码如下:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) { if navigationAction.shouldPerformDownload { decisionHandler(.download, preferences) } else { decisionHandler(.allow, preferences) } } func webView(_ webView: WKWebView, navigationAction: WKNavigationAction, didBecome download: WKDownload) { download.delegate = self } func webView(_ webView: WKWebView, navigationResponse: WKNavigationResponse, didBecome download: WKDownload) { download.delegate = self } func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) { if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { let url = dir.appendingPathComponent(suggestedFilename) completionHandler(url) } }
根因分析
错误码102对应WebKit错误WebKitErrorFrameLoadInterruptedByPolicyChange,即帧加载被策略变更中断,结合回调表现,可能原因如下:
- 仅在请求发起阶段判断下载标识,未处理响应阶段的下载识别:
navigationAction.shouldPerformDownload仅在请求发起阶段就能明确判定为下载时才会返回true,如果服务端未在请求阶段给出下载标识、仅在响应头返回Content-Disposition: attachment,或者返回的MIME类型是WKWebView无法渲染的二进制类型,请求阶段无法识别为下载,允许导航后就会因为内容无法渲染触发加载中断。 - 网页下载逻辑由前端JS实现,不走原生导航流程:如果按钮点击后是通过
fetch/XHR拉取PDF二进制数据,再通过Blob+动态a标签的方式触发前端下载,这类操作不会触发WKWebView的原生导航下载逻辑,shouldPerformDownload始终为false,最终因为Blob URL无法被主帧加载触发102错误。 - 跨域请求被拦截:如果PDF生成接口和当前网页主域不一致,且服务端未配置合法的CORS响应头,WKWebView会拦截跨域请求,直接触发预加载失败,不会进入后续下载流程。
- 下载代理回调实现不全:当前未实现
decidePolicyFor navigationResponse的导航响应代理方法,无法在响应阶段主动将无法渲染的内容标记为下载,也未实现下载失败回调,无法捕获下载流程中的具体错误。
修复方案
- 补全导航响应阶段的下载判断逻辑,不要仅依赖系统在请求阶段的自动识别:
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { // 无法直接渲染的MIME类型,标记为下载 guard navigationResponse.canShowMIMEType else { decisionHandler(.download) return } // 响应头标记为附件的内容,标记为下载 if let httpResponse = navigationResponse.response as? HTTPURLResponse, let contentDisposition = httpResponse.allHeaderFields["Content-Disposition"] as? String, contentDisposition.lowercased().contains("attachment") { decisionHandler(.download) return } decisionHandler(.allow) } - 针对前端Blob类型的下载,需要注入JS脚本拦截前端的Blob、a标签下载行为,通过JSBridge将文件数据传递给原生层,由原生完成本地文件写入和系统下载调度,这类纯前端触发的下载不会走WKWebView原生下载回调。
- 检查PDF生成接口的跨域配置,确保接口返回合法的
Access-Control-Allow-Origin等CORS头,避免跨域拦截导致的请求失败。 - 补全WKDownload的失败代理回调,捕获下载阶段的具体错误便于排查:
func download(_ download: WKDownload, didFailWithError error: Error, resumeData: Data?) { print("下载失败:\(error.localizedDescription)") }
内容的提问来源于stack exchange,提问作者Ginso
相关产品推荐
相关产品推荐

