Customize PDF View Controller States on iOS

PDFViewController is PSPDFKit’s principle UI component for displaying documents, and it can be in five different states:

StateScreenshot
DefaultScreenshot of default state showing the first page of the PSPDFKit 11 Quickstart Guide document.
LoadingScreenshot of loading state with a progress bar.
EmptyScreenshot of empty state with message “No Document Set.”
LockedScreenshot of locked state with a password text field. Locked: Enter the password.
ErrorScreenshot of error state with error message “Unable to Display Document: The document couldn’t be accessed.”

You can see each of these states in ControllerStateExample(opens in a new tab) in the PSPDFKit Catalog app on GitHub.

Take a look at the ControllerState API reference for more information.

Custom Overlay UI

You can change the state’s strings and images, or you can set PDFViewController.overlayViewController to take care of state handling yourself.

To create an overlay view controller, you have to implement the ControllerStateHandling protocol in a UIViewController subclass.

Implementing all of the states, with the exception of locked, is pretty straightforward, because they don’t feature any interaction. To unlock documents, you need to add a text field and handle the keyboard accordingly. Use Document.unlock(withPassword:) to unlock the document. After that, you need to reload the PDFViewController with reloadData().

The following code snippets should help you correctly create your own overlay view controller:

class OverlayViewController: UIViewController, ControllerStateHandling {
// MARK: Properties
weak var pdfController: PDFViewController!
private let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = .gray
label.textAlignment = .center
return label
}()
private let textField: UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.isSecureTextEntry = true
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.borderStyle = .roundedRect
return textField
}()
private let button: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Unlock", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.sizeToFit()
return button
}()
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action: #selector(unlock), for: .touchUpInside)
let stackView = UIStackView(arrangedSubviews: [label, textField, button])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 20
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.widthAnchor.constraint(equalToConstant: 300),
stackView.heightAnchor.constraint(equalToConstant: 150),
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
// MARK: Button Actions
@objc
private func unlock(sender: UIButton) {
guard let document = document, let password = textField.text else { return }
document.unlock(withPassword: password)
pdfController.reloadData()
}
// MARK: ControllerStateHandling
var document: Document?
public func setControllerState(_ state: ControllerState, error: Error?, animated: Bool) {
var text = ""
var backgroundColor: UIColor? = .white
switch state {
case .default:
backgroundColor = nil
case .empty:
text = "No document set"
case .loading:
text = "Loading..."
case .locked:
text = "Password:"
case .error:
text = "Unable to display document:\n\(error!.localizedDescription)"
}
label.text = text
view.backgroundColor = backgroundColor
view.isUserInteractionEnabled = state != .default
if state == .locked {
textField.isHidden = false
button.isHidden = false
textField.becomeFirstResponder()
} else {
textField.isHidden = true
button.isHidden = true
textField.resignFirstResponder()
}
}
}

Connect the custom overlay view controller to the PDF view controller like this:

let pdfController = PDFViewController(document: document)
let overlayViewController = OverlayViewController()
overlayViewController.pdfController = pdfController
pdfController.overlayViewController = overlayViewController