---
title: "Customize PDF view controller states in iOS viewer | Nutrient SDK"
canonical_url: "https://www.nutrient.io/guides/ios/customizing-the-interface/state-customization/"
md_url: "https://www.nutrient.io/guides/ios/customizing-the-interface/state-customization.md"
last_updated: "2026-06-08T19:21:59.248Z"
description: "Explore the five states of Nutrient’s PDFViewController: Default, Loading, Empty, Locked, and Error, to enhance document display functionality."
---

# Customize PDF view controller states on iOS

[`PDFViewController`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/pdfviewcontroller) is Nutrient’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`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/pdfviewcontroller/documentviewcontroller).

- **Loading** — A document is loading. This is the case when one or more of the [`document`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/pdfviewcontroller/document)’s [`dataProviders`](https://www.nutrient.io/api/ios/documentation/pspdfkit/document/dataproviders) is providing a [`progress`](https://www.nutrient.io/api/ios/documentation/pspdfkit/dataproviding/progress). For example, this occurs if the document is being [opened from a remote URL](https://www.nutrient.io/../../miscellaneous/document-downloads/).

- **Empty** — No document is set. In other words, the [`document`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/pdfviewcontroller/document) property is `nil`.

- **Locked** — A document is [password protected](https://www.nutrient.io/../../open-a-document/password-protected-pdfs/).

- **Error** — There was an error when trying to load or open a document, which can be read using the [`controllerStateError`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/pdfviewcontroller/controllerstateerror) property.

| State   | Screenshot                                                                                                                                                                                           |
| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Default |![Screenshot of default state showing the first page of the PSPDFKit 11 Quickstart Guide document.](@/assets/guides/ios/user-interface/pdf-view-controller/controller-states/default.png)            |
| Loading |![Screenshot of loading state with a progress bar.](@/assets/guides/ios/user-interface/pdf-view-controller/controller-states/loading.png)                                                            |
| Empty   |![Screenshot of empty state with message “No Document Set.”](@/assets/guides/ios/user-interface/pdf-view-controller/controller-states/empty.png)                                                     |
| Locked  |![Screenshot of locked state with a password text field. Locked: Enter the password.](@/assets/guides/ios/user-interface/pdf-view-controller/controller-states/locked.png)                           |
| Error   |![Screenshot of error state with error message “Unable to Display Document: The document couldn’t be accessed.”](@/assets/guides/ios/user-interface/pdf-view-controller/controller-states/error.png) |

You can see each of these states in [`ControllerStateExample`](https://github.com/PSPDFKit/pspdfkit-ios-catalog/blob/master/Catalog/Examples/ControllerCustomization/ControllerStateExample.swift) in the PSPDFKit Catalog app on GitHub.

Take a look at the [`ControllerState`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/controllerstate) API reference for more information.

## Custom overlay UI

You can [change the state’s strings](https://www.nutrient.io/../../features/localization/#add-further-localization-to-pspdfkit) and [images](https://www.nutrient.io/../../customizing-the-interface/changing-an-image-used-in-pspdfkit), or you can set [`PDFViewController.overlayViewController`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/pdfviewcontroller/overlayviewcontroller) to take care of state handling yourself.

To create an overlay view controller, you have to implement the [`ControllerStateHandling`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/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:)`](https://www.nutrient.io/api/ios/documentation/pspdfkit/document/unlock(withpassword:)) to unlock the document. After that, you need to reload the [`PDFViewController`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/pdfviewcontroller) with [`reloadData()`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/pdfviewcontroller/reloaddata()).

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

### SWIFT

```swift

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()
        }
    }
}

```

### OBJECTIVE-C

```objc

@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:

### SWIFT

```swift

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

```

### OBJECTIVE-C

```objc

PSPDFViewController *pdfController = [[PSPDFViewController alloc] initWithDocument:document];
OverlayViewController *overlayViewController = [OverlayViewController new];
overlayViewController.pdfController = pdfController;
pdfController.overlayViewController = overlayViewController;

```
---

## Related pages

- [Configure PDF view controllers on iOS](/guides/ios/getting-started/view-controller-configuration.md)
- [Embedding PDF view controllers on iOS](/guides/ios/customizing-the-interface/embedding-the-pdfviewcontroller-inside-a-custom-container-view-controller.md)
- [Customize the display of PDFs with the view hierarchy](/guides/ios/customizing-the-interface/the-document-view-hierarchy.md)

