This guide will give you an overview of the Instant API and how to use it for displaying documents from the server, in addition to talking about the lifecycle documents go through while being displayed.

API Overview

The entry class into Instant is InstantClient. This represents a connection to your PSPDFKit Server. An app typically creates one InstantClient, although it could create several to connect to multiple PSPDFKit Server instances.

Instant manages downloading and storing PDF files when your app requests this. Each PDF file managed by Instant is identified by its identifier and can have one or more named layers. Each layer represents a separate set of annotations displayed on top of the base file and is uniquely identified by the combination of identifier and layerName.

The API to each layer managed by Instant is InstantDocumentDescriptor. A document descriptor may be used to request downloading the associated PDF file and annotations by calling download(usingJWT:) with an appropriate JSON Web Token (JWT). It can also be used to create a Document. Set this document on an InstantViewController and then show it to the user. The view controller will show a download progress bar and then refresh to display the PDF when the download completes. Changes to annotations in the Document will be seen by all users viewing the same layer.

InstantViewController is a subclass of PDFViewController and may be used to show any Document — not just those created by Instant. However, there won’t be any annotation synchronization for documents that weren’t created by Instant. This means it’s possible to create a PDFTabbedViewController whose internal pdfController is an InstantViewController. You can also mix documents managed by Instant with other documents in the tabbed bar. Showing a Document created by an InstantDocumentDescriptor in any PDFViewController except an InstantViewController isn’t supported.

Document Lifecycle

When using a PDF file managed by Instant, you might go through these stages:

  • Obtaining a JWT for a layer from your server

  • Obtaining a document descriptor for the layer encoded in the JWT from the InstantClient

  • Using the document descriptor to download the layer data with the JWT

  • Showing the document descriptor’s editableDocument and synchronizing annotations

  • Updating the JWT to keep synchronizing annotations

  • Losing access to the layer

  • Clearing the local storage for the layer or the document identifier

You can read more about the lifecycle of a document descriptor in our Understanding the Document State guide.

Usage in Detail

After signing in to your server and getting the JWT for the layer you want to show to the user, obtain a matching document descriptor from Instant:

do {
    let instantClient = try InstantClient(serverURL: URL(string: "location of your PSPDFKit Server here")!)
    let JWT = "a JWT for some layer from your server"
    let documentDescriptor = try instantClient.documentDescriptor(forJWT: JWT)
} catch {
    // Handle error.
PSPDFInstantClient *client = [[PSPDFInstantClient alloc] initWithServerURL:[NSURL URLWithString:@"location of your PSPDFKit Server here"]];
NSString *JWT = @"a JWT for some layer from your server";

NSError *error;
id<PSPDFInstantDocumentDescriptor> documentDescriptor = [client documentDescriptorForJWT:JWT error:&error];
if (documentDescriptor == nil) {
    NSLog(@"This JWT is invalid: %@", error);

To display the layer to the user, you need to show it in an InstantViewController and download the layer’s data. This can happen in either order, depending on your needs. If you show the view controller while the layer’s data is still downloading, PSPDFKit will display a progress bar and automatically refresh when the download finishes.

Show an InstantViewController and set its document to a Document managed by Instant:

let pdfDocument = documentDescriptor.editableDocument
let pdfViewController = InstantViewController(document: pdfDocument)
self.navigationController?.pushViewController(pdfViewController, animated: true)
// or
self.present(pdfViewController, animated: true)
PSPDFDocument *pdfDocument = documentDescriptor.editableDocument;
PSPDFInstantViewController *pdfViewController = [[PSPDFInstantViewController alloc] initWithDocument:pdfDocument];
[self.navigationController pushViewController:pdfViewController animated:YES];
// or
[self presentViewController:pdfViewController animated:YES completion:NULL];

Check if the layer data has been downloaded by using the isDownloaded property on the document descriptor. If not, use the JWT for this layer to start the download. This would typically be asynchronous, so you need to avoid making multiple concurrent requests for the same JWT:

let JWT = "JWT for the layer from your server"
do {
    try JWT)
} catch {
    print("Could not start downloading layer '\(documentDescriptor.layerName)' of document '\(documentDescriptor.identifier)': \(error)")
NSString *JWT = @"token from your server";

NSError *error;
if (![documentDescriptor downloadUsingJWT:JWT error:&error]) {
    NSLog(@"Could not start downloading layer '%@' of document '%@': %@", documentDescriptor.layerName, documentDescriptor.identifier, error);

When the download finishes, InstantViewController will refresh automatically. The Instant client’s delegate will be notified by a call to instantClient(_:didFinishDownloadFor:), and PSPDFInstantDidFinishDownload will be posted with the document descriptor as the object.

Updating the JWT for a Layer

Instant doesn’t permanently store JWTs, and the JWTs have an expiration date. When a new JWT is needed for a layer, the instantClient(_:didFailAuthenticationFor:) delegate method will be called, and PSPDFInstantDidFailReauthentication will be posted with the corresponding InstantDocumentDescriptor as the object. This will always be the case the first time syncing a document descriptor after app launch.

When your app receives this callback, it should request a new JWT for the document descriptor from your server and pass this JWT to Instant to keep synchronizing annotations:

let JWT = "new JWT for (documentDescriptor.identifier, documentDescriptor.layerName) from your server"
documentDescriptor.reauthenticate(withJWT: JWT)
NSString *JWT = "new JWT for (documentDescriptor.identifier, documentDescriptor.layerName) from your server";
[documentDescriptor reauthenticateWithJWT:JWT];

When it’s successful, the document descriptor will post a PSPDFInstantDidFinishReauthentication, and instantClient(_:documentDescriptor:didFinishReauthenticationWithJWT:) will be called on your InstantClientDelegate, letting you know it’s safe to persist the JWT (e.g. in the keychain). Should reauthentication fail, the document descriptor will post a PSPDFInstantDidFailReauthentication, and instantClient(_:documentDescriptor:didFailReauthenticationWithError:) will be called on your InstantClientDelegate instead.

Losing Access to a Layer

Your app might find out that a user no longer has access to a layer from your server or from PSPDFKit Server. In that case, you’ll probably want to stop showing the document and clear its local storage:

do {
    try documentDescriptor.removeLocalStorage()
} catch {
    // Errors are unlikely, but it is possible the delete from the file system fails.
NSError *error;

if (![documentDescriptor removeLocalStorageWithError:&error]) {
    // Errors are unlikely, but it is possible the delete from the file system fails.

ℹ️ Note: Because all document descriptors with the same identifier share the same PDF file, InstantDocumentDescriptor.removeLocalStorage() only removes the annotation data for the layer with the name stored in the document descriptor’s layerName property. To get rid of the PDF file for a given identifier, along with all associated layer data, use InstantClient.removeLocalStorage(forDocumentIdentifier:) instead. If you simply want to purge all PDF files for which no layer data is available, use InstantClient.removeUnreferencedCacheEntries().