Open a PDF file with Swift(PDFKit, WKWebView)
PDF files are common place and with iOS/iPadOS becoming a more powerful operating system, opening PDF files and other regular computer type tasks will become more common place.
In this tutorial we explore two ways of opening a PDF file with Swift. The PDF can be a local file or a hosted file but we will be using a local file. If you want to use a hosted file it will work in basically the same way. Both methods that we are going to use today allows for us to use a URL
when trying to load the PDF, so it doesn't matter whether the URL
is a local file or not.
Even though this tutorial is not focussed on opening a remote PDF, I will be adding the code at the end of each section that will allow you to open a remote PDF file. You can also find the full source code for this tutorial here.
Now that we know what we are doing, let's get to the code:
Step 1: Get URL for local PDF
As I mentioned earlier in this tutorial we will be focussing on how to open a local PDF file. In order to do that we need to have a PDF file that we can add to our project. To do that I have used the following PDF file - https://web.stanford.edu/class/archive/cs/cs161/cs161.1168/lecture4.pdf. I have named this file heaps.pdf
, so in the code below you will see that I use the file name heaps
.
I will not be including the PDF file in the repo as I am not sure what license it has and if I am allowed to distribute it.
Because we are adding this PDF to our project we can use Bundle.main
to get the file's URL
. To do this we need to create the following method:
private func resourceUrl(forFileName fileName: String) -> URL? {
if let resourceUrl = Bundle.main.url(forResource: fileName,
withExtension: "pdf") {
return resourceUrl
}
return nil
}
This method will take the file name as the argument. If the file exists then we will return the resourceUrl
, if it does not exist we will return nil
.
We will use this method for both the WKWebView
and PDFKit
.
NOTE: All the code for this tutorial will be in my ViewController
file as I am working with a new project and this is a tutorial. If this wasn't a tutorial I would put this code in a more appropriate place.
Open PDF with WKWebView
Now that we have our resourceUrl
method that will return the URL
for our PDF file, we can implement our createWebView
method. Just before we do that, we need to import WebKit
.
Add the following import to your file:
import WebKit
Ok, now that we have WebKit
imported, we can create our new method. This new method will create a new WKWebView
and then it will load the URL
and return the webview that was created. But, this will only happen if a PDF exists, if one doesn't then this method will return nil.
private func createWebView(withFrame frame: CGRect) -> WKWebView? {
let webView = WKWebView(frame: frame)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
if let resourceUrl = self.resourceUrl(forFileName: "heaps") {
webView.loadFileURL(resourceUrl,
allowingReadAccessTo: resourceUrl)
return webView
}
return nil
}
The above method takes a frame
as an argument so that we can set the WKWebView
frame when we initialise it.
After that we set the autoResizingMask
with .flexibleWidth
and .flexibleHeight
.
Now we will use the resourceUrl
method that we created in Step 1. We call the resourceUrl
method with heaps
as the fileName
argument value, this is the name of my PDF file, please change this name to whatever you have called your PDF file. If resourceUrl
returns a URL
we load it in the webview by using loadFileUrl
. If it returns nil, we will return nil because the webview will be empty.
If you want a full tutorial on loading local files with a WKWebView, I have written one that you can find here.
Displaying the webview
Now that we have created a webview to display the PDF, we need to show the webview. To do this we will create a new method called displayWebView
, which will look like this:
private func displayWebView() {
if let webView = self.createWebView(withFrame: self.view.bounds) {
self.view.addSubview(webView)
}
}
Nothing complicated here. We call the createWebView
method with the our view's bounds. If this method returns a webview we will add it as a subview to self.view
, if it returns nil
then nothing will happen.
We can now display the webview by calling the displayWebView
in our viewDidLoad
method. Update your viewDidLoad
method to look like the following:
override func viewDidLoad() {
super.viewDidLoad()
self.displayWebView()
}
If you build and run the app now you should see the following:
Open remote PDF with WKWebView
Loading a local PDF file into a WKWebView
is easy, but loading one from a remote URL
is even easier because we do not need to use the resourceUrl
method. If you want to open the PDF from the url of the file that I linked in Step 1, you can update the createWebView
method to the following:
private func createWebView(withFrame frame: CGRect) -> WKWebView? {
let webView = WKWebView(frame: frame)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
if let resourceUrl = URL(string: "https://web.stanford.edu/class/archive/cs/cs161/cs161.1168/lecture4.pdf") {
let request = URLRequest(url: resourceUrl)
webView.load(request)
return webView
}
return nil
}
Instead of calling resourceUrl
we used URL(string: )
which will return a URL
if the string that we pass through is valid.
Opening a remote PDF cannot be done by using loadFileURL
, because loadFileURL
is only for local files. So in order to load a remote URL
we need to create a URLRequest
and pass the URLRequest
to the WKWebView
method called load
.
If you build and run the app now, you will see that the PDF loads as expected, however, it will take longer because it needs to download the file.
Open PDF with PDFKit
If you skipped Step 1, please ensure that you read that before continuing. In Step 1 we created the resourceUrl
method that will return the file URL
.
When using PDFKit
there are two main classes that we are going to use. These two classes are PDFView
and PDFDocument
.
PDFView
is a subclass of UIView
and it will be the view that shows the PDF file. PDFDocument
loads the PDF file and allows us to set the document
property on the PDFView
.
Before we can use these classes we need to import PDFKit
. To do that add the following import to your file:
import PDFKit
Now that we have imported PDFKit
we can start creating the methods that will allow us to use it.
The first method we will create is the createPdfView
. This method will take a frame as an argument. We will use this when we initialise our PDFView
.
Add the following code to your file:
private func createPdfView(withFrame frame: CGRect) -> PDFView {
let pdfView = PDFView(frame: frame)
pdfView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
pdfView.autoScales = true
return pdfView
}
This method will initialise a PDFView
, it will set the autoResizingMask
and then we will set autoScales
as true. If we don't set the autoScales
the scale of the PDF document won't be correct. If you want you can set this manually, but for this tutorial using autoScales
is fine. Once the PDFView
has be setup we return it.
We can now use the resourceUrl
method and create a PDFDocument
. To do that, add the following method to your code:
private func createPdfDocument(forFileName fileName: String) -> PDFDocument? {
if let resourceUrl = self.resourceUrl(forFileName: fileName) {
return PDFDocument(url: resourceUrl)
}
return nil
}
This method will take the file name of the file we want to open. We pass that file name to resourceUrl
, if the file exists we create a PDFDocument
, if it doesn't exist we will return nil.
All the hard work is now done. Let's create our display method. Add the following code:
private func displayPdf() {
let pdfView = self.createPdfView(withFrame: self.view.bounds)
if let pdfDocument = self.createPdfDocument(forFileName: "heaps") {
self.view.addSubview(pdfView)
pdfView.document = pdfDocument
}
}
In this method we call createPdfView
and we pass self.view.bounds
to set the frame of the PDFView
. Next, we call createPdfDocument
in an if let
. If the PDFDocument
could be created, we will add pdfView
as a subview to self.view
and we will assign pdfDocument
to pdfView.document
so that when the view gets shown, the PDF will be visible.
The last thing that we need to do now is call displayPdf
from our viewDidLoad
. Update your viewDidLoad
to the following:
override func viewDidLoad() {
super.viewDidLoad()
self.displayPdf()
}
If you build and run the app now you will see the following:
This looks almost identical when comparing it to the webview implementation. There is one visual difference besides the scaling. When using a PDFView
, we don't have a page count in the top left corner, where the webview does.
Open remote PDF file with PDFKit
To open a remote PDF with PDFKit
we need to change one line in our createPdfDocument
method.
Update createPdfDocument
method to look like this:
private func createPdfDocument(forFileName fileName: String) -> PDFDocument? {
if let resourceUrl = URL(string: "https://web.stanford.edu/class/archive/cs/cs161/cs161.1168/lecture4.pdf") {
return PDFDocument(url: resourceUrl)
}
return nil
}
The only change in the above method is removing the call to self.resourceUrl
and replacing it with URL(string: )
. If you build and run the app now, everything will work as expected, except it will take a bit longer for the PDF to be displayed because it needs to be downloaded.
Conclusion:
Either of these methods make it easy to open PDF files, whether the file is local to the device or not. Deciding on which to use is up to you. One way might suit your needs to better, it all depends what your requirements are.
If you want to see the full source, you can find it here.