Usage

API Overview

The entry class into Instant is PSPDFInstantClient. This represents a connection to your PSPDFKit Server. An app typically creates one PSPDFInstantClient, 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 PSPDFInstantDocumentDescriptor. A document descriptor may be used to request downloading the associated PDF file and annotations by calling downloadUsingJWT:error: with an appropriate JSON Web Token (JWT) and to create a PSPDFDocument. Set this document on a PSPDFInstantViewController 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 PSPDFDocument will be seen by all users viewing the same layer.

PSPDFInstantViewController is a subclass of PSPDFViewController and may be used to show any PSPDFDocument — not just those created by Instant. However, there will be no annotation synchronization for documents that were not created by Instant. This means it is possible to create a PSPDFTabbedViewController whose internal pdfController is a PSPDFInstantViewController. You can also mix documents managed by Instant with other documents in the tabbed bar. Showing a PSPDFDocument created by a PSPDFInstantDocumentDescriptor in any PSPDFViewController except a PSPDFInstantViewController is not 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 PSPDFInstantClient
  • 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:

Copy
1
2
3
4
5
6
7
8
9
let instantClient = PSPDFInstantClient(serverURL: URL(string: "location of your PSPDFKit Server here")!)
let JWT = "a JWT for some layer from your server"

do {
    let documentDescriptor = try instantClient.documentDescriptor(forJWT: JWT)
} catch {
    print("This JWT is invalid: \(error)")
    return
}
Copy
1
2
3
4
5
6
7
8
9
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);
    return;
}

To display the layer to the user, you need to show it in a PSPDFInstantViewController 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 a PSPDFInstantViewController and set its document to a PSPDFDocument managed by Instant:

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

Check if the layer data has been downloaded 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:

Copy
1
2
3
4
5
6
let JWT = "JWT for the layer from your server"
do {
    try documentDescriptor.download(usingJWT: JWT)
} catch {
    print("Could not start downloading layer '\(documentDescriptor.layerName)' of document '\(documentDescriptor.identifier)': \(error)")
}
Copy
1
2
3
4
5
6
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, PSPDFInstantViewController will refresh automatically. The Instant client’s delegate will be notified by a call to instantClient:didFinishDownloadForDocumentDescriptor:, and PSPDFInstantDidFinishDownloadNotification will be posted with the document descriptor as the object.

Updating the JWT for a Layer

Instant does not permanently store JWTs, and the JWTs have an expiry date. When a new JWT is needed for a layer, the instantClient:didFailAuthenticationForDocumentDescriptor: delegate method will be called and PSPDFInstantDidFailAuthenticationNotification will be posted with the corresponding PSPDFInstantDocumentDescriptor as the object. This will always be the case during 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:

Copy
1
2
let JWT = "new JWT for (documentDescriptor.identifier, documentDescriptor.layerName) from your server"
documentDescriptor.reauthenticate(withJWT: JWT)
Copy
1
2
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 PSPDFInstantDidFinishReauthenticationNotification, and instantClient:documentDescriptor:didFinishReauthenticationWithJWT: will be called on your PSPDFInstantClientDelegate, letting you know it is safe to persist the JWT (e.g. in the keychain). Should reauthentication fail, the document descriptor will post a PSPDFInstantDidFailReauthenticationNotification and instantClient:documentDescriptor:didFailReauthenticationWithError: will be called on your PSPDFInstantClientDelegate 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 the PSPDFKit Server. In that case, you will probably want to stop showing the document and clear its local storage:

Copy
1
2
3
4
5
do {
    try documentDescriptor.removeLocalStorage()
} catch {
    // Errors are unlikely, but it is possible the delete from the file system fails.
}
Copy
1
2
3
4
5
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, -[id<PSPDFInstantDocumentDescriptor> removeLocalStorageWithError:] only removes the annotation data for that particular layer. To get rid of the PDF file for a given identifier, as well as all associated layer data, use -[PSPDFInstantClient removeLocalStorageForDocumentIdentifier:error:] instead. If you wish to simply purge all PDF files for which no layer data is available anymore, use -[PSPDFInstantClient removeUnreferencedCacheEntries:].