Available Now: Explore our latest release with enhanced accessibility and powerful IDP features
By David Luco | 2019 Jun 05
5 min
Tags
tutorial
iOS
swift
view
In this article we describe how to add a PDF viewer to your iOS app using SwiftUI, Apple's declarative UI framework announced at WWDC 2019.
Integrating the Apryse iOS SDK for a project that uses SwiftUI is the same as for a "standard" Swift project. Please see our integration guide for more information.
In your SwiftUI project integrated with Apryse iOS, create a new SwiftUI View file (Command+N -> Select "SwiftUI View") named DocumentView.swift
and import the PDFNet
and Tools
modules at the top of the Swift file, below the existing SwiftUI
import:
import SwiftUI
import PDFNet
import Tools
The DocumentView.swift
file contains a placeholder View
struct type that we will not use, so it can be removed. We can now declare an empty DocumentView
struct type that conforms to the UIViewControllerRepresentable
protocol. For this example, we will be wrapping the PTDocumentViewController
class, a full-featured PDF and document viewer and annotator.
struct DocumentView : UIViewControllerRepresentable {
}
The UIViewControllerRepresentable
protocol is required to create a SwiftUI View
that represents a UIKit UIViewController
. There are two required functions in the protocol that must be implemented, the makeUIViewController(context:)
and updateUIViewController(_:context:)
functions. The first function is called to give us a chance to create and return a view controller instance (a PTDocumentViewController
), and the second is called to update the view controller when an update is necessary.
Add these two functions to the DocumentViewer
struct with the following implementations:
func makeUIViewController(context: Context) -> PTDocumentViewController {
// Create and return a new PTDocumentViewController instance.
return PTDocumentViewController()
}
func updateUIViewController(_ uiViewController: PTDocumentViewController, context: Context) {
// Empty.
}
It's now possible to display the SwiftUI-wrapped PTDocumentViewController
from the UIWindowSceneDelegate
lifecycle function scene(_:willConnectTo:options:)
. A basic implementation of this function looks like the following:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use a UIHostingController as window root view controller.
let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = UIHostingController(rootView: DocumentView())
self.window = window
window.makeKeyAndVisible()
}
The UIHostingController
, a UIViewController
subclass, is a class whose purpose is to wrap a SwiftUI View
(a DocumentView
in this case) for use in UIKit. This could be thought of as the opposite of the UIViewControllerRepresentable
protocol: UIHostingController
makes SwiftUI available from UIKit, and UIViewControllerRepresentable
makes UIKit acessible from SwiftUI.
Running the project shows the following empty viewer:
An empty SwiftUI PDF viewer.
The next step is to load a document in the viewer. First, add a url
property to the DocumentView
struct, and use it to open a document in the makeUIViewController(context:)
function:
struct DocumentView : UIViewControllerRepresentable {
var url: URL?
func makeUIViewController(context: Context) -> PTDocumentViewController {
// Create and return a new PTDocumentViewController instance.
let documentViewController = PTDocumentViewController()
// Check if there was a URL specified.
if let url = url {
// Open the document at the specified URL.
documentViewController.openDocument(with: url)
}
return documentViewController
}
// ...
}
Then a URL that points to a document needs to be passed into the DocumentView
created in the UIWindowSceneDelegate
:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// ...
// Create a URL for a remote PDF file.
let url = URL(string: "https://pdftron.s3.amazonaws.com/downloads/pl/PDFTRON_mobile_about.pdf")
// Pass the URL into the DocumentView struct's memberwise initializer.
window.rootViewController = UIHostingController(rootView: DocumentView(url: url))
// ...
}
If you run the app, it will now look like this:
A working SwiftUI PDF viewer.
Navigation stacks in SwiftUI are handled with a NavigationView
view. While this view does contain a UINavigationController
internally, it is not possible to populate its navigation bar with UIBarButtonItem
s from a SwiftUI-wrapped UIViewController
. This is because the wrapped view controller's parent is a UIHostingController
that is required for the SwiftUI to UIKit bridging. The UIHostingController
is the view controller placed onto the (UIKit) navigation stack, not the SwiftUI-wrapped UIViewController
. To work around this limitation, the PTDocumentViewController
can be placed within a UINavigationController
that is then placed within a SwiftUI view.
The PTDocumentViewController
class uses the top navigation bar provided by a UINavigationController
to display its buttons for searching, sharing, annotating, and more. Currently the DocumentView
does not have a navigation bar but this can be added by wrapping the PTDocumentViewController
instance in a UINavigationController
:
func makeUIViewController(context: Context) -> UINavigationController {
// ...
let navigationController = UINavigationController(rootViewController: documentViewController)
return navigationController
}
The type of the UIViewController
parameter in the updateUIViewController(_:context:)
method also needs to be updated to the UINavigationController
class:
func updateUIViewController(_ navigationController: UINavigationController, context: Context) {
// Empty.
}
Now the DocumentView
will have a navigation bar displaying all the PTDocumentViewController
's buttons:
The viewer with a navigation bar and buttons.
The default behavior of SwiftUI View
s is to respect the edges of the safe area. However, for a fullscreen SwiftUI View
it is necessary to ignore the edges of the safe area. This can be done at the time the DocumentView
is created in the UIWindowSceneDelegate
using the edgesIgnoringSafeArea(_:)View
modifier:
PDFDocumentView(url: url)
.edgesIgnoringSafeArea(.all)
The DocumentView
will now extend all the way to the top and bottom of the screen.
For simple use cases where performance, rendering, and available features are not as important, it is also possible to use Apple's PDFKit framework to add a basic PDF viewer. We'll briefly go over how to show a PDFView
in a SwiftUI app.
First a new PDFKitView
struct needs to be declared. To wrap a UIView
instead of a UIViewController
, the struct type needs to conform to the UIViewRepresentable
protocol. In the makeUIView(context:)
function a PDFView
is created and returned, optionally opening a PDFDocument
with the provided URL.
import SwiftUI
import PDFKit
struct PDFKitView : UIViewRepresentable {
var url: URL?
func makeUIView(context: Context) -> UIView {
let pdfView = PDFView()
if let url = url {
pdfView.document = PDFDocument(url: url)
}
return pdfView
}
func updateUIView(_ uiView: UIView, context: Context) {
// Empty
}
}
The PDFKitView
can be used in the same way as the DocumentView
by passing a document URL to the struct's memberwise initializer. However, only PDF documents can be opened by the PDFKit viewer. The PTDocumentViewController
-based SwiftUI view can open PDFs, Office (Word, Powerpoint, Excel) and iWork (Pages, Keynote, Numbers) documents, image files, and more.
In this article we showed how to add a PDF viewer to a SwiftUI app with Apryse and PDFKit. We're excited by the release of SwiftUI and look forward to working more with the framework in the coming months.
If you have any questions about integrating Apryse into your project, please feel free to contact us and we'll be more than happy to help!
Tags
tutorial
iOS
swift
view
David Luco
Related Products
Share this post
PRODUCTS
Enterprise
Small Business
Popular Content