如何在Xamarin.Forms iOS的WebView中实现文件下载?
Xamarin.Forms iOS WebView 文件下载实现方案
一、是否需要自定义渲染器?
是的,Xamarin.Forms 原生WebView未提供下载事件的直接绑定能力,iOS端必须通过自定义渲染器基于WKWebView实现下载监听逻辑。
二、解决URL为null的问题
你遇到的URL为空问题,是因为OnElementChanged触发时机早于ViewModel的URL属性初始化。可以通过监听Element的PropertyChanged事件,在URL属性更新时再加载网页,修改后的渲染器代码如下:
[assembly: ExportRenderer(typeof(WebView), typeof(MyWebViewRenderer_iOS))] namespace MPS.Libertas.Mobile.iOS.Renderers { public class MyWebViewRenderer_iOS : ViewRenderer<WebView, WKWebView> { WKWebView _wkWebView; private WebView _formsWebView; protected override void OnElementChanged(ElementChangedEventArgs<WebView> e) { base.OnElementChanged(e); // 清理旧元素监听 if (e.OldElement != null) { _formsWebView.PropertyChanged -= OnFormsWebViewPropertyChanged; } if (e.NewElement != null) { _formsWebView = e.NewElement; // 监听Source属性变化 _formsWebView.PropertyChanged += OnFormsWebViewPropertyChanged; if (Control == null) { var config = new WKWebViewConfiguration(); _wkWebView = new WKWebView(Frame, config); // 设置导航代理处理下载逻辑 _wkWebView.NavigationDelegate = new WebViewNavigationDelegate(); SetNativeControl(_wkWebView); } // 初始检查URL是否已设置 LoadUrlIfAvailable(); } } private void OnFormsWebViewPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == nameof(WebView.Source)) { LoadUrlIfAvailable(); } } private void LoadUrlIfAvailable() { if (_formsWebView.Source is UrlWebViewSource urlSource && !string.IsNullOrEmpty(urlSource.Url)) { Control.LoadRequest(new NSUrlRequest(new NSUrl(urlSource.Url))); } } protected override void Dispose(bool disposing) { if (disposing) { _formsWebView?.PropertyChanged -= OnFormsWebViewPropertyChanged; _wkWebView?.NavigationDelegate?.Dispose(); _wkWebView?.Dispose(); } base.Dispose(disposing); } } }
三、实现下载链接监听与处理
通过WKNavigationDelegate拦截下载请求,或在iOS 13+使用WKDownloadDelegate实现原生下载处理,两种方案如下:
方案1:通用拦截(支持iOS全版本)
创建导航代理类,通过URL后缀或MIME类型判断是否为下载请求:
public class WebViewNavigationDelegate : WKNavigationDelegate { public override void DecidePolicyForNavigationAction(WKWebView webView, WKNavigationAction navigationAction, Action<WKNavigationActionPolicy> decisionHandler) { var request = navigationAction.Request; var url = request.Url.AbsoluteString; // 根据文件后缀判断是否为下载链接,可按需扩展 if (IsDownloadableFile(url)) { // 拦截请求,手动处理下载 StartFileDownload(request.Url); decisionHandler(WKNavigationActionPolicy.Cancel); return; } decisionHandler(WKNavigationActionPolicy.Allow); } private bool IsDownloadableFile(string url) { var downloadExtensions = new[] { ".pdf", ".docx", ".xlsx", ".zip", ".rar", ".apk" }; return downloadExtensions.Any(ext => url.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); } private void StartFileDownload(NSUrl url) { var session = NSUrlSession.SharedSession; var downloadTask = session.DownloadTask(url, (tempLocation, response, error) => { if (error != null) { Console.WriteLine($"下载失败:{error.LocalizedDescription}"); return; } // 将临时文件移动到Documents目录 var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); var destUrl = NSUrl.FromFilename(Path.Combine(docPath, response.SuggestedFilename)); try { NSFileManager.DefaultManager.Move(tempLocation, destUrl, out NSError moveError); if (moveError == null) { // 主线程弹窗提示用户 InvokeOnMainThread(() => { var alert = UIAlertController.Create("下载完成", $"文件已保存至:{destUrl.Path}", UIAlertControllerStyle.Alert); alert.AddAction(UIAlertAction.Create("确定", UIAlertActionStyle.Default, null)); UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null); }); } } catch (Exception ex) { Console.WriteLine($"文件移动失败:{ex.Message}"); } }); downloadTask.Resume(); } }
方案2:iOS 13+ 原生下载处理
若App仅支持iOS 13及以上,可使用WKDownloadDelegate实现更原生的下载流程:
- 在渲染器中添加下载代理:
// 初始化WKWebView时添加 _wkWebView.DownloadDelegate = new WebViewDownloadDelegate();
- 创建下载代理类:
public class WebViewDownloadDelegate : WKDownloadDelegate { public override void DidStart(WKWebView webView, WKDownload download) { Console.WriteLine("下载开始"); // 可在此添加进度显示逻辑 } public override void DidFinish(WKWebView webView, WKDownload download) { var tempUrl = download.DestinationUrl; var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); var destUrl = NSUrl.FromFilename(Path.Combine(docPath, download.Response.SuggestedFilename)); try { NSFileManager.DefaultManager.Move(tempUrl, destUrl, out NSError moveError); if (moveError == null) { InvokeOnMainThread(() => { var alert = UIAlertController.Create("下载完成", $"文件已保存至:{destUrl.Path}", UIAlertControllerStyle.Alert); alert.AddAction(UIAlertAction.Create("确定", UIAlertActionStyle.Default, null)); UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null); }); } } catch (Exception ex) { Console.WriteLine($"文件移动失败:{ex.Message}"); } } public override void DidFail(WKWebView webView, WKDownload download, NSError error) { Console.WriteLine($"下载失败:{error.LocalizedDescription}"); } }
四、注意事项
- 若访问HTTP链接,需在
Info.plist中配置NSAppTransportSecurity的NSAllowsArbitraryLoads以绕过HTTPS强制校验 - 如需让用户在App外打开下载文件,可结合
UIDocumentInteractionController实现 - 大文件下载建议添加进度条展示,提升用户体验
内容的提问来源于stack exchange,提问作者Lucas Ribeiro
相关产品推荐
相关产品推荐

