Jamie Leech
Jamie Leech

Reputation: 57

View a PDF in WebView Issue (Xamarin.ios)

Im currently having an issue loading a local pdf into a webview. I have the code which works without any errors and when I run it on the iPad simulator, it works absolutely perfect. However, the issue comes when I try to run it on a physical iPad device. When I run it and it gets to the point where it needs to show the PDF, the webview loads but there is no PDF shown in the webview.

The PDF is actually generated by the app and I store it inside a directory inside the library folder.

Code to show the PDF in the WebView:

public void LoadPdfToWebView(string pdfPath)
    {
        //Console.WriteLine("Load request started");

        WebView.LoadRequest(new NSUrlRequest(new NSUrl(pdfPath, false)));
        View.AddSubview(WebView);

        //Console.WriteLine("Load request Finished");
    }

Not really sure why this would be the case and hopefully somebody can help.

Upvotes: 0

Views: 2382

Answers (2)

duindain
duindain

Reputation: 545

I've just had to fix this for an app and thought I'd post the solution

This is for WKWebView which is a requirement from Apple as of Dec 2020 though the deadline has been temporarily extended

Xaml PdfWebView ContentPage

<controls:PdfWebView
  Source="{Binding PDFSource}"
  HeightRequest="1000"
  WidthRequest="1000"/>

control

namespace XForms.Controls
{
    public class PdfWebView : WebView { }
}

VM, only the relevant part

private string _pdfSource;
public string PDFSource
{
      get => _pdfSource;
      set
      {
           if (Device.RuntimePlatform == Device.Android && value.StartsWith("file:") == false)
           {
                value = $"file:///android_asset/pdfjs/web/viewer.html?file=file:///{WebUtility.UrlEncode(value)}";
           }
           SetProperty(ref _pdfSource, value);
      }
}

iOS renderer for PdfWebView

using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using XForms.Controls;
using WebKit;
using Foundation;

[assembly: ExportRenderer(typeof(PdfWebView), typeof(iOSUI.Renderers.PdfWebViewRenderer))]
namespace iOSUI.Renderers
{
    public class PdfWebViewRenderer : ViewRenderer<WebView, WKWebView>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
        {
            base.OnElementChanged(e);

            if (Control == null)
            {
                var wkWebViewConfiguration = new WKWebViewConfiguration();

                var wkWebView = new WKWebView(Frame, wkWebViewConfiguration)
                {
                    AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
                };
                SetNativeControl(wkWebView);
            }
            if (e.NewElement != null)
            {
                if (string.IsNullOrEmpty(((UrlWebViewSource)e.NewElement.Source)?.Url) == false)
                {
                    var url = ((UrlWebViewSource)e.NewElement.Source).Url;
                    if(url.StartsWith("http"))
                    {
                        Control.LoadRequest(new NSUrlRequest(new NSUrl(url)));
                    }
                    else
                    {
                        Control.LoadFileUrl(new NSUrl($"file://{url}"), new NSUrl($"file://{url}"));
                    }
                }
            }
        }
    }
}

Android Renderer

using System.Net;
using Android.Content;
using Android.Views;
using Android.Webkit;
using XForms.Controls;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(PdfWebView), typeof(AndroidUI.Renderers.PDFViewRenderer))]
namespace AndroidUI.Renderers
{
    public class PDFViewRenderer : WebViewRenderer
    {
        public PDFViewRenderer(Context context) : base(context) { }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement != null)
            {
                Control.Settings.JavaScriptEnabled = true;
                Control.Settings.DomStorageEnabled = true;
                Control.Settings.AllowFileAccess = true;
                Control.Settings.AllowFileAccessFromFileURLs = true;
                Control.Settings.AllowUniversalAccessFromFileURLs = true;

                Control.SetWebChromeClient(new WebChromeClient());
            }
        }

        // If you want to enable scrolling in WebView uncomment the following lines.
        public override bool DispatchTouchEvent(MotionEvent e)
        {
            Parent.RequestDisallowInterceptTouchEvent(true);
            return base.DispatchTouchEvent(e);
        }
    }
}

This solution uses pdfjs in Android and WKWebview in iOS to render the PDF

The PDFSource is the full path to the file, I use System.IO .net standard calls to handle this in a cross platform way

All the files are stored in (I have a method called GetFullPath to return the cross platform common path)

Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)

Combined with a filename with Path.Combine

Path.Combine(GetFullPath(), fileName);

That is the PDFSource that gets set in the VM

The Pdfjs library files are just copied into Assets/pdfjs for Android

The magic for iOS is just calling LoadFileUrl instead of LoadRequest and prepending "file://"

I've slightly sanitised our namespaces so some of them wont resolve like XForms.Controls and so on that refer to our internal code

Upvotes: 1

Junior Jiang
Junior Jiang

Reputation: 12723

In Xamarin.IOS to show a document type other than HTML in a UIWebView:

  1. Add the document (for example, a PDF) to your Xamarin.iOS project. Set the Build Action to BundleResource. You can set the build action for a file by right-clicking on that file and and choosing Build Action in the menu that opens.

  2. Create a UIWebView and add it to a view:

    webView = new UIWebView (View.Bounds);
    View.AddSubview(webView);
    
  3. Load the file using NSUrl and NSUrlRequest classes:

    string fileName = "Loading a Web Page.pdf"; // remember case-sensitive
    string localDocUrl = Path.Combine (NSBundle.MainBundle.BundlePath, fileName);
    webView.LoadRequest(new NSUrlRequest(new NSUrl(localDocUrl, false)));
    webView.ScalesPageToFit = true;
    

You can refer to this officical steps.If have problems or other needs, you can refer to this link

If you can't read the resources in the bundle, you can put the resource cache in the temp directory of the sandbox and try to read it using LoadRequest.

Upvotes: 0

Related Questions