iOS PDF Editor API Usage

The Document Editor is a PSPDFKit feature comprised of a set of MVC classes. It provides you and your users with a whole host of page editing features, including: new page creation, page duplication, copying and pasting, reordering, rotation, deletion, and the creation of new documents from a subset of selected pages.

This guide describes how you can use the document editing API in your application. To learn about the built-in UI, see the Document Editing UI guide.

Class Overview

In addition to a comprehensive UI, the Document Editor also comes with under-the-hood access to Document Editor model classes, which you can use to: perform document editing in code, extend the built-in document editing UI, or build an entirely custom document editing user interface.

Here’s an overview of classes you might need to access to perform document editing operations:

Class Description
PDFDocumentEditor Main class for performing all supported document editing operations
PDFEditingChange Represents a change that was performed by PDFDocumentEditor
PDFNewPageConfiguration Represents configuration options for newly created pages

Editing

To begin document editing on a Document, you first need to create a PDFDocumentEditor. This is done using the init?(document:) initializer. You can then perform document editing operations on the resulting Document Editor by invoking its various instance methods. Each document-mutating method returns an array of PDFEditingChange objects, which you can use to update your user interface (if relevant). The change objects are also sent to all configured Document Editor delegates. If you’re inserting a new page, you’ll also have to create a PDFNewPageConfiguration object that you then pass to the PDFDocumentEditor.addPages(in:with:) method.

The Document Editor keeps track of the performed operations and offers built-in undo/redo support. Checking the canUndo property is also a good way to determine if the Document Editor requires saving.

Saving

When you’re done with the performed changes, you need to call one of the save methods to preserve your changes. You have two options available:

  • save(completionBlock:) — This method writes the updated PDF document to the same file path used by the source document, overwriting the source document in the process. The use of this method has some restrictions. The document needs to be in a writable directory and needs to be comprised of a single document provider. You can use the canSave property to determine if a save is possible. The method performs the save operation asynchronously and provides a reference to the updated document in its completion block. A save will clear all caches related to the previous document state to avoid any inconsistencies.

  • save(toPath:withCompletionBlock:) — This save method can be used to perform a Save As operation. In this case, the changed PDF document is written to the provided path (you need to make sure it is writable by the application). This method is also performed asynchronously. The completion block in this case returns a newly created document. This method is always available, regardless of the canSave state. Note, however, that if you use the save(toPath:withCompletionBlock:) method to overwrite a document that’s currently loaded, you’ll need to explicitly handle unloading (or reloading) of that document, including clearing all caches. Failing to do so might result in caching issues and/or exceptions.

ℹ️ Note: The changes performed on the Document Editor object aren’t reflected in the associated Document until the changes get saved. Choosing to not save a Document Editor object is thus equivalent to discarding the performed changes.

Usage Example

Here’s an example that rotates the first two pages and inserts a new page at the front of the currently loaded document of a PDFViewController:

let document = self.document
guard let editor = PDFDocumentEditor(document: document) else { return }

// Rotate the first two pages 90 degrees.
editor.rotatePages([0, 1], rotation: 90)

// Add a new page as the first page.
let pageTemplate = PageTemplate(pageType: .tiledPatternPage, identifier: .grid5mm)
let newPageConfiguration = PDFNewPageConfiguration(pageTemplate: pageTemplate) {
    $0.pageSize = document.pageInfoForPage(at: 0)!.size
    $0.backgroundColor = UIColor(white: 0.95, alpha: 1)
}
editor.addPages(in: NSRange(location: 0, length: 1), with: newPageConfiguration)

// Save and overwrite the document.
editor.save { document, error in
    if let error = error {
        print("Document editing failed: \(error)")
        return
    }
    // Access the UI on the main thread.
    DispatchQueue.main.async {
        self.pdfController.reloadData()
    }
}
PSPDFDocument *document = self.document;
PSPDFDocumentEditor *editor = [[PSPDFDocumentEditor alloc] initWithDocument:document];
if (!editor) return;

// Rotate the first two pages 90 degrees.
[editor rotatePages:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)] rotation:90];

// Add a new page as the first page.
PSPDFPageTemplate *pageTemplate = [[PSPDFPageTemplate alloc] initWithPageType:PSPDFNewPageTypeTiledPatternPage identifier:PSPDFTemplateIdentifierGrid5mm];
PSPDFNewPageConfiguration *newPageConfiguration = [PSPDFNewPageConfiguration newPageConfigurationWithPageTemplate:pageTemplate builderBlock:^(PSPDFNewPageConfigurationBuilder *builder) {
    builder.pageSize = [document pageInfoForPageAtIndex:0].size;
    builder.backgroundColor = [UIColor colorWithWhite:0.95f alpha:1.f];
}];
[editor addPagesInRange:NSMakeRange(0, 1) withConfiguration:newPageConfiguration];

// Save and overwrite the document.
[editor saveWithCompletionBlock:^(PSPDFDocument *savedDocument, NSError *error) {
    if (error) {
        NSLog(@"Document editing failed: %@", error);
        return;
    }
    // Access the UI on the main thread.
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.pdfController reloadData];
    });
}];

New Document Creation

The Document Editor API can also be used to create new PDF documents from scratch. To do so, initialize PDFDocumentEditor without a PDFViewController. You can then use the regular PDFDocumentEditor API to add and modify pages. When done, save the new document to a location on the file system:

guard let documentEditor = PDFDocumentEditor(document: nil) else { return }

// Add the first page. At least one is needed to be able to save the document.
let pageTemplate = PageTemplate(pageType: .emptyPage, identifier: .blank)
let newPageConfiguration = PDFNewPageConfiguration(pageTemplate: pageTemplate) {
    $0.pageSize = CGSize(width: 595, height: 842); // A4 in points
}
documentEditor.addPages(in: NSRange(location: 0, length: 1), with: newPageConfiguration)

// Save to a new PDF file.
documentEditor.save(toPath: saveToPath) { document, error in
    if let error = error {
        print("Error saving document. Error: \(error)")
    }
}
PSPDFDocumentEditor *documentEditor = [[PSPDFDocumentEditor alloc] init];
if (!documentEditor) return;

// Add the first page. At least one is needed to be able to save the document.
PSPDFPageTemplate *pageTemplate = [[PSPDFPageTemplate alloc] initWithPageType:PSPDFNewPageTypeTiledPatternPage identifier:PSPDFTemplateIdentifierGrid5mm];
PSPDFNewPageConfiguration *newPageConfiguration = [PSPDFNewPageConfiguration newPageConfigurationWithPageTemplate:pageTemplate builderBlock:^(PSPDFNewPageConfigurationBuilder *builder) {
    builder.pageSize = CGSizeMake(595, 842); // A4 in points
}];
[documentEditor addPagesInRange:NSMakeRange(0, 1) withConfiguration:newPageConfiguration];

// Save to a new PDF file.
[documentEditor saveToPath:saveToPath withCompletionBlock:^(PSPDFDocument *document, NSError *error) {
    if (error) {
        NSLog(@"Error saving document. Error: %@", error);
    }
}];

Import and Export

The PDFDocumentEditor API can also be used either to create a new document from a subset of the current document’s pages or to import pages from an outside document.

To export a set of pages, use PDFDocumentEditor.exportPages(_:toPath:withCompletionBlock:) and provide a destination path for the generated PDF. The method will write out a new PDF file and call the completion block when the operation finishes.

To import pages, call PDFDocumentEditor.importPages(to:from:withCompletionBlock:queue:) This method takes a Document, which needs to be a fileURL-based document, and inserts all of its pages at the provided page index in the currently edited document. Just like with any other Document Editor operation, importing new pages doesn’t actually modify the edited document until saving; the Document Editor simply records a reference to the other document and its pages. To reliably save the imported pages, the imported document PDF data needs to be preserved until the editing session is saved. PDFDocumentEditor.importPages(to:from:withCompletionBlock:queue:) handles this for you automatically.

The import and export methods are also used to implement PDFDocumentEditorToolbarController-level copy and paste operations.

Availability

Currently, not all Documents can be edited using the Document Editor. At the moment, document editing is limited to PDFs that store all their annotations inside the PDF file. In practice, this means document editing is limited to Documents that only use PDFFileAnnotationProviders without external storage. PDFFileAnnotationProvider stores annotations externally if the document’s annotationSaveMode is set to .externalFile or if it’s set to .embeddedWithExternalFileAsFallback (the default) in combination with a PDF located at a file path to which your application can’t write.

If you want to guarantee that document editing and annotating will always be possible for your documents, it’s recommended to always ensure that the documents you’re working with are located in a directory your app can write to (for instance, the app’s Documents directory) before loading them.

If you initialize the Document Editor with an unsupported document, initialization fails and the initializer returns nil.