Open a PDF from Server on iOS

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.

For a more complete example application, please refer to our guide on getting started with real-time collaboration, which shows the expected lifetimes of the key components of Instant in better detail.

Document Lifecycle

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

  • Obtaining a JSON Web Token (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 guide on document state.

Detailed Use

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 Document Engine here")!)
    let JWT = "a JWT for some layer from your server"
    let documentDescriptor = try instantClient.documentDescriptor(forJWT: JWT)
    // IMPORTANT: The `instantClient` needs to be kept alive for as long
    // as you want to work with Instant. Store it in a property, generally
    // of your `AppDelegate`.
} catch {
    // Handle error.
}
PSPDFInstantClient *client = [[PSPDFInstantClient alloc] initWithServerURL:[NSURL URLWithString:@"location of your Document Engine 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;
}
// IMPORTANT: The `instantClient` needs to be kept alive for as long
// as you want to work with Instant. Store it in a property, generally
// of your `AppDelegate`.

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 documentDescriptor.download(usingJWT: 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 Document Engine. 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.
}
Information

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()`.