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:
-
Default — A document is being displayed. This is the only state in which the PDF view controller will have a non-
nil
documentViewController
. -
Loading — A document is loading. This is the case when one or more of the
document
’sdataProviders
is providing aprogress
. For example, this occurs if the document is being opened from a remote URL. -
Empty — No document is set. In other words, the
document
property isnil
. -
Locked — A document is password protected.
-
Error — There was an error when trying to load or open a document, which can be read using the
controllerStateError
property.
State | Screenshot |
---|---|
Default | |
Loading | |
Empty | |
Locked | |
Error |
You can see each of these states in ControllerStateExample
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() } } }
@interface OverlayViewController : UIViewController <PSPDFControllerStateHandling> @property (nonatomic, weak) PSPDFViewController *pdfController; @end @interface OverlayViewController () @property (nonatomic) UILabel *label; @property (nonatomic) UITextField *textField; @property (nonatomic) UIButton *button; @end @implementation OverlayViewController #pragma mark - UIViewController -(void)viewDidLoad { [super viewDidLoad]; self.label = [UILabel new]; self.label.translatesAutoresizingMaskIntoConstraints = NO; self.label.numberOfLines = 0; self.label.textColor = [UIColor grayColor]; self.label.textAlignment = NSTextAlignmentCenter; self.textField = [UITextField new]; self.textField.translatesAutoresizingMaskIntoConstraints = NO; self.textField.secureTextEntry = YES; self.textField.autocorrectionType = UITextAutocorrectionTypeNo; self.textField.autocapitalizationType = UITextAutocapitalizationTypeNone; self.textField.borderStyle = UITextBorderStyleRoundedRect; self.button = [UIButton buttonWithType:UIButtonTypeSystem]; self.button.translatesAutoresizingMaskIntoConstraints = NO; [self.button setTitle:@"Unlock" forState:UIControlStateNormal]; [self.button setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; [self.button sizeToFit]; [self.button addTarget:self action:@selector(unlock:) forControlEvents:UIControlEventTouchUpInside]; UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[self.label, self.textField, self.button]]; stackView.translatesAutoresizingMaskIntoConstraints = NO; stackView.axis = UILayoutConstraintAxisVertical; stackView.distribution = UIStackViewDistributionFillEqually; stackView.spacing = 20; [self.view addSubview:stackView]; [NSLayoutConstraint activateConstraints:@[ [stackView.widthAnchor constraintEqualToConstant:300], [stackView.heightAnchor constraintEqualToConstant:150], [stackView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor], [stackView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor] ]]; } #pragma mark - Button Actions - (void)unlock:(id)sender { NSString *password = self.textField.text; [self.document unlockWithPassword:password]; [self.pdfController reloadData]; } #pragma mark - PSPDFControllerStateHandling @synthesize document; -(void)setControllerState:(PSPDFControllerState)state error:(NSError *)error animated:(BOOL)animated { NSString *text = @""; UIColor *backgroundColor = [UIColor whiteColor]; switch (state) { case PSPDFControllerStateDefault: backgroundColor = nil; break; case PSPDFControllerStateEmpty: text = @"No document set"; break; case PSPDFControllerStateLoading: text = @"Loading..."; break; case PSPDFControllerStateLocked: text = @"Password:"; break; case PSPDFControllerStateError: text = [NSString stringWithFormat:@"Unable to display document:\n%@", error.localizedDescription]; break; default: break; } self.label.text = text; self.view.backgroundColor = backgroundColor; self.view.userInteractionEnabled = state != PSPDFControllerStateDefault; if (state == PSPDFControllerStateLocked) { self.textField.hidden = NO; self.button.hidden = NO; [self.textField becomeFirstResponder]; } else { self.textField.hidden = YES; self.button.hidden = YES; [self.textField resignFirstResponder]; } } @end
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
PSPDFViewController *pdfController = [[PSPDFViewController alloc] initWithDocument:document]; OverlayViewController *overlayViewController = [OverlayViewController new]; overlayViewController.pdfController = pdfController; pdfController.overlayViewController = overlayViewController;