iOS原生App中WKWebView需实现webView(_:runOpenPanelWith:completionHandler:)代理方法才能启用input type="file"上传,否则静默失败;HTML端须由用户手势直接触发且满足三项约束。

iOS 原生 App 无法直接“调用 HTML5 文件上传”——input type="file" 在 WKWebView 中默认被禁用,点击无响应,也不会触发选择器。这不是 bug,是 iOS 系统级限制。
WKWebView 中 input type="file" 不弹出选择器的真正原因
WKWebView 默认不处理文件选择请求,系统不会自动桥接到 PHPickerViewController 或 UIDocumentPickerViewController。即使 HTML 写对了,click 事件触发、change 事件也永远不会来。
- iOS 14.5+ 要求显式实现
webView(_:didReceive:)并拦截WKNavigationAction中的文件选择意图(实际不走这个路径) - 真正需要的是实现
WKUIDelegate的webView(_:runOpenPanelWith:completionHandler:)方法 - 该方法只在用户点击
input[type=file]时由 WKWebView 主动调用,是唯一合法入口 - 未实现此代理方法 → 直接静默失败,控制台无报错,DevTools 看不到任何异常
必须实现的代理方法:webView(_:runOpenPanelWith:completionHandler:)
这个方法不是可选的“增强功能”,而是启用文件上传的强制开关。漏掉它,整个流程就卡死在这里。
- 必须返回
true,否则 WKWebView 认为你拒绝处理 -
parameters.allowedFileTypes是网页指定的accept属性值(如["public.image", "com.adobe.pdf"]),需转成UTType或扩展名数组传给选择器 - 推荐用
PHPickerViewController(iOS 14+):支持多选、相册/文件App 混合源、无需沙盒权限 - 若需兼容 iOS 13,fallback 到
UIDocumentPickerViewController,但要额外声明NSDocumentPickerUsageDescription - 选完后必须调用
completionHandler([URL]),传入本地临时 URL(不能是 assets-library:// 或 ph://)
HTML 端要注意的三个硬性约束
光靠原生侧配置还不够,HTML 必须满足以下条件,否则 WKWebView 根本不会触发代理方法:
立即学习“前端免费学习笔记(深入)”;
-
必须是用户手势直接触发(如onclick、onchange绑定的按钮),不能由setTimeout或 Promise resolve 后自动.click() - 必须设置
webkitdirectory属性才能选文件夹(仅限 iOS 16.4+),且需额外申请PHPhotoLibrary权限 - 不要依赖
event.target.files[0].arrayBuffer()直接读大文件 —— WKWebView 的 JSContext 对Blob支持不完整,建议用fetch(fileUrl)或FileReader.readAsDataURL转 base64
常见错误现象与定位方式
如果上传没反应,先确认以下几点,比翻文档更快:
- 控制台是否输出
[Process] 0x10482c200 - [pageProxyID=17, webPageID=18, PID=21497] WebPageProxy::didFailProvisionalLoadForFrame: frameID = 3, domain = NSURLErrorDomain, code = -999?这是代理未实现的典型日志 - 在
webView(_:runOpenPanelWith:completionHandler:)开头加断点,看是否命中 —— 不命中 = HTML 触发方式非法 或 代理未正确 set - 用
location.href = "file:///var/mobile/Containers/Data/Application/.../tmp/upload.jpg"手动测试 URL 是否可被 WebView 加载,排除沙盒路径问题 - 真机测试!模拟器的
PHPickerViewController行为与真机不同,常假成功
最易被忽略的是:WKWebView 初始化后必须立即设置 uiDelegate,且不能晚于 loadHTMLString(_:baseURL:);一旦页面开始加载,再设代理就无效了。这个时机差几毫秒都会导致整个上传链路静默失效。










