Skip to main content

Showing PDF files in Xamarin.Forms

·6 mins

This will be just a quick-and-dirty blog on showing PDF files in your Xamarin.Forms application. I got the question from an Italian friend who wanted to implement this into an app, so I thought I’d share it with the rest of you!

All code used in this post can be found here: https://github.com/jfversluis/pdfjs

The problem #

Showing a PDF file seems a very easy task, and depending on what platform you are targeting; it is. For iOS, you can basically simply load your file into a WebView and that’s that. It will show nicely. You will just have to add a teensy bit of magic to enable zooming, but that is it.

For Android and Windows it is a bit different. They don’t have a default PDF viewer and you can’t rely on your users to have their own viewer. Ideally, you would want to keep your users in your app when showing a PDF file.

The solution #

Let’s have a look at how we can solve this in a Xamarin.Forms way. I will start with the shared Forms code and then implement iOS, since that is the easiest one.

Although not a 100% necessary, I have also included some code to download the PDF file to local storage first. This way we could choose to cache it and not having to download it every time. You could choose to just load the external URL and show that.

Shared #

In the shared code I have a very simple page which shows a WebView. Normally you would probably not have this page as a first, but somewhere in your app when navigating to an invoice for example. The idea remains the same, provide a URL and load it into a page with a WebView. The UI can be see underneath.

The custom PdfWebView control is literally nothing more than just a inheritance of the regular WebView. This is needed so I can create a custom renderer for it. The code-behind for this page is a bit more extensive. Of course, you would want to use MVVM, but remember; this is a quick-and-dirty blog ;-)

In the constructor a URL is provided which is then processed through the ILocalFileProvider, which is the dependency service code to save the file to the right place and tell us where that place is. Another thing that you will also notice is that there is a distinction between Android and the rest. Depending on what platforms you will implement you need smarter if-statements or just a better approach altogether, but this will give you the general idea.

Before we move on to the platform code, notice that at the end of the constructor, for Android we set a special URL, while for iOS we just set the WebView source to the local file path. More on that later.

iOS #

For iOS we can just load the PDF into a WebView control and it will show nicely. As I said; I have implemented some code to download the file first. So basically, what happens is I provide the downloaded stream already together with a filename, this gets saved and gives me back the local file path and I load that into the WebView. To provide the correct local path, we will need some DependencyService code.

Underneath you see the code to do all this.

So now the local file path is returned, that is set as the WebView URL and all is well.

For iOS there is one thing we need to add. To enable zooming of the PDF, we need to implement a custom renderer to set the  ScalesPageToFit property on the native WKWebView. This can be done easily by implementing this code on the iOS project:

Android #

Now for Android (and Windows variants as well, but I didn’t implement those) some more work needs to be done. Android does not just show PDF file nicely in the WebView like iOS does. To overcome this there are several routes to take. I have chosen to incorporate the pdf.js library into my app and show it with that. There even is a mobile optimized view incorporated. More on the library can be found here: https://mozilla.github.io/pdf.js/

The first thing we need to do is download the pdf.js library and add it to our Android Assets in the pdfjs folder. Make sure the build action for all files is set to AndroidAsset. Have a look at the screenshot below for my project structure.

s

pdf.js library in the Android Assets

The code to save the file to your local device on Android looks like the code underneath.

It actually looks very similar to the iOS code, but the difference is that variables which retrieve path names produce different values on each platform. That is the main reason why we need a implementation on each platform we want to do this.

Now with this in place, the rest is basically the same idea. If you refer back to the pdfjsPage.xaml.cs code above, you will remember that when we load the URL into the WebView it looked different than the one on iOS. The URL will look more like:

PdfView.Source = $“file:///android_asset/pdfjs/web/viewer.html?file={WebUtility.UrlEncode(localPath)}”;

With the knowledge you have now, you will recognize that we actually first load the pdf.js library from our assets and with that we provide it the local file path in a query parameter that is built-in.

Update Jan 2019 #

A small update on this. It has been brought to my attention by John (a fellow Xamarin developer from the Netherlands), that the PDF sample did not work when you updated the target SDK to Android 8.1. After some investigation, I noticed this error in the console output:

Access to XMLHttpRequest at 'file:///storage/emulated/0/pdfjs/07b0c522-e5bc-419a-9ec9-b2e66292100b.pdf' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.", source: file:///android_asset/pdfjs/build/pdf.worker.js (43028)

So, apparently, Android has upped their game in the cross origin policy area. I found out that there are some extra settings available in the Android WebView that will allow you to circumvent this. I whipped up this custom renderer for it.

As you can see there are a couple of settings that you can set to true to allow the loading of local files. I’m not absolutely sure that all of them are necessary, so you might want to look into that yourself for a bit. The updated working sample is on the GitHub repo!

At the same time I got a question in the form of an issue on GitHub, asking how to open PDF files locally from embedded resources. If you take a look at the issue, you should see how this can be done: https://github.com/jfversluis/pdfjs/issues/1

All done! #

And when you now run this sample code, you can show PDFs inline with your application, awesome!

All code for this can be found on my Github page, and as always: if there are any questions or comments, please don’t hesitate to let me know!