mauimaui-ios

Download files in MAUI iOS WebView


I have a WebView in a MAUI app that generally works, but whenever I click a link on iOS that is supposed to download a file (link returns a Content-Disposition header) it is opened in the WebView. I would like it to be downloaded (and opened in the default iOS app) instead.

How is this supposed to be implemented? There is apparently a IWKDownloadDelegate interface with a DecideDestination() method but I can't find examples of how to wire it all up in MAUI. I got it working on Android by writing some platform-specific code, and I imagine something similar can be done for iOS.

<WebView
    x:Name="WebView"
    Source=".." />
public partial class WebClientPage : ContentPage
{
    public WebClientPage()
    {
        InitializeComponent();
    }

    protected override void OnHandlerChanged()
    {
        base.OnHandlerChanged();

#if IOS
        var iosWebView = WebView.Handler.PlatformView as WebKit.WKWebView;

        // Otherwise swiping doesn't work
        iosWebView.AllowsBackForwardNavigationGestures = true;
#endif
    }
}

Related question for Android: Download files in MAUI Android WebView


Solution

  • You can download files on iOS by handling the DecideDestination event of the WKDownloadDelegate. Also, you need to implement IWKDownloadDelegate and IWKNavigationDelegate and override the default response to show the downloaded file. Here's a solution provided by Tim for your reference:

    using ObjCRuntime;
    using WebKit;
    
    namespace WebviewTestCatalyst;
    
    [Register("AppDelegate")]
    public class AppDelegate : UIApplicationDelegate
    {
        public override UIWindow? Window { get; set; }
        public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
        {
            // create a new window instance based on the screen size
            Window = new UIWindow(UIScreen.MainScreen.Bounds);
    
            // create a UIViewController with a single UILabel
            var vc = new UIViewController();
            var webview = new TestDownloadWebView(Window!.Frame, new WebKit.WKWebViewConfiguration())
            {
                AutoresizingMask = UIViewAutoresizing.All
            };
    
            vc.View!.AddSubview(webview);
            Window.RootViewController = vc;
            // make the window visible
            Window.MakeKeyAndVisible();
            webview.LoadRequest(new NSUrlRequest(
                new NSUrl("https://file-examples.com/index.php/sample-documents-download/sample-pdf-download/")));
            return true;
        }
    
        public class TestDownloadWebView : WKWebView, IWKDownloadDelegate, IWKNavigationDelegate
        {
            public TestDownloadWebView(CGRect frame, WKWebViewConfiguration configuration) : base(frame, configuration)
            {
                this.NavigationDelegate = this;
            }
            public void DecideDestination(WKDownload download, NSUrlResponse response, string suggestedFilename,
                Action<NSUrl> completionHandler)
            {
                var destinationURL = GetDestinationURL();
                completionHandler?.Invoke(destinationURL);
            }
    
            [Export("webView:decidePolicyForNavigationResponse:decisionHandler:")]
            public void DecidePolicy(WKWebView webView, WKNavigationResponse navigationResponse, Action<WKNavigationResponsePolicy> decisionHandler)
            {
                var url = navigationResponse.Response.Url;
                var mimeType = navigationResponse.Response.MimeType;
                Console.WriteLine($"Content-Type: {mimeType}");
                // Perform any actions based on the content type
                if (mimeType == "application/pdf")
                {
                    // Download the PDF file separately instead of loading it in the WKWebView
                    DownloadPDF(url);
    
                    decisionHandler?.Invoke(WKNavigationResponsePolicy.Cancel);
                }
                else
                {
                    decisionHandler?.Invoke(WKNavigationResponsePolicy.Allow);
                }
               
            }
    
            private void DownloadPDF(NSUrl url)
            {
                var downloadTask = NSUrlSession.SharedSession.CreateDownloadTask(url, (location, _, error) =>
                {
                    if (location is NSUrl sourceURL && error == null)
                    {
                        var destinationURL = GetDestinationURL();
    
                        try
                        {
                            NSFileManager.DefaultManager.Move(sourceURL, destinationURL, out error);
                            Console.WriteLine($"PDF file downloaded and saved at: {destinationURL.Path}");
    
                            // Perform any additional actions with the downloaded file
                        }
                        catch (Exception ex)
                        {
                            // Handle file moving error
                        }
                    }
                    else
                    {
                        // Handle download error
                    }
                });
    
                downloadTask.Resume();
            }
    
            private NSUrl GetDestinationURL()
            {
                // Customize the destination URL as desired
                var documentsURL =
                    NSFileManager.DefaultManager.GetUrls(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomain.User)
                        [0];
                var destinationURL = documentsURL.Append("downloaded_file.pdf", false);
    
                return destinationURL;
            }
        }
    }