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-
nildocumentViewController. - Loading — A document is loading. This is the case when one or more of the
document’sdataProvidersis 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
documentproperty 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
controllerStateErrorproperty.
| State | Screenshot |
|---|---|
| Default | ![]() |
| Loading | ![]() |
| Empty | ![]() |
| Locked | ![]() |
| Error | ![]() |
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() } }}@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]; }}
@endConnect the custom overlay view controller to the PDF view controller like this:
let pdfController = PDFViewController(document: document)let overlayViewController = OverlayViewController()overlayViewController.pdfController = pdfControllerpdfController.overlayViewController = overlayViewControllerPSPDFViewController *pdfController = [[PSPDFViewController alloc] initWithDocument:document];OverlayViewController *overlayViewController = [OverlayViewController new];overlayViewController.pdfController = pdfController;pdfController.overlayViewController = overlayViewController;




