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

如何在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实现更原生的下载流程:

  1. 在渲染器中添加下载代理:
// 初始化WKWebView时添加
_wkWebView.DownloadDelegate = new WebViewDownloadDelegate();
  1. 创建下载代理类:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 10:37:22