iOS PDF Bookmark SDK

Bookmarks provide a convenient way of marking where you last stopped in your PDF documents. The PDF spec doesn’t contain a standardized way to store bookmarks in PDF documents, so for this reason, PSPDFKit will store bookmarks as part of the XMP metadata in a way that’s compatible with PSPDFKit on all platforms, as well as with Apple Preview.

Working with Bookmarks

For most use cases, you don’t need to interact with the bookmarks model yourself, as PSPDFKit provides a UI for users to add, remove, and sort bookmarks depending on the configuration in use. It then automatically stores them in the PDF document they belong to. However, when you want to provide either your own UI or your own bookmarks store, you need to access the model that PSPDFKit provides for you.

The bookmarks model essentially consists of two classes: Bookmark, which is the bookmark itself and contains information about its name and what to do if a user selects a bookmark; and BookmarkManager, which is the container that keeps track of all the bookmarks of a document. You can access the bookmark manager through Document.bookmarkManager. This can then be used to retrieve, sort, add, and remove bookmarks.

Accessing Bookmarks

To access bookmarks, there are two methods available. You can just call bookmarks on a bookmark manager to get a list of bookmarks. These bookmarks are unsorted, and the order of the list can change at any point. This can be used if you want to know how many bookmarks there are or whether a particular bookmark is part of the bookmark manager. The other method is bookmarks(with:), which gets the sort order passed in and then returns a sorted array of bookmarks. This method is unrelated to the bookmarks API. The returned array will be sorted according to the specified sort order, but it doesn’t alter the state of the bookmark manager.

The available sort orders are .custom and .pageBased. The sort order used by PSPDFKit when showing bookmarks to the user is set in PDFConfiguration.bookmarkSortOrder. If you want to use the UI from PSPDFKit alongside your own logic, make sure to use the same sort order. Otherwise, you may get unexpected results.

The custom sort order is managed by PSPDFKit internally. Adding bookmarks adds them to the end of the list and the user can manually sort them. The UI for sorting bookmarks is automatically provided by the bookmarks view controller when this is the sort option set in the configuration.

The page-based sort order simply sorts bookmarks based on the page they’re referring to. This only works for bookmarks that use a GoToAction as their associated action. Convenience methods to access the page are available through a category defined by the go-to action. Bookmarks that don’t refer to a page will be sorted at the end of the list and the bookmark manager will make sure the sorting is always stable.

Adding and Removing Bookmarks

To add bookmarks, call addBookmark(_:) on the bookmark manager, passing in the bookmark you want to add to the bookmark manager. If you instead want to remove a bookmark, call removeBookmark(_:). If you want these changes to be stored, you need to save the associated document, which in turn will make sure the bookmark manager saves its content. By default, PSPDFKit automatically saves changes to a document at specific points.

Updating Bookmarks

Bookmark is an immutable object. If you want to make changes on a bookmark, you can create a mutable copy of the bookmark, change the part you want to change, and then add the bookmark again by calling addBookmark(_:). In this case, the bookmark manager will replace the old bookmark with the new one. For this to work, it’s important that you actually modify an existing bookmark. Don’t create a new bookmark e.g. on the same page but with a different name. Doing so will result in two bookmarks pointing to the same page but with different names:

let bookmarks = bookmarkManager.bookmarks(with: .pageBased)
if let first = bookmarks.first?.mutableCopy() as? MutableBookmark {
    first.name = "First Bookmark"
    bookmarkManager.addBookmark(first)
}
NSArray<PSPDFBookmark *> *bookmarks = [bookmarkManager bookmarksWithSortOrder:PSPDFBookmarkManagerSortOrderPageBased];
PSPDFMutableBookmark *first = bookmarks.firstObject.mutableCopy;
if (first) {
	first.name = @"First Bookmark";
	[bookmarkManager addBookmark:first];
}

Implementing Your Own Custom Bookmark Provider

Handling Bookmarks

If you want to use your own store to save bookmarks, this can be done by implementing your own BookmarkProvider. Each bookmark manager has an array of bookmark providers, and each bookmark provider participates in providing, adding, and removing bookmarks. You can create your own bookmark provider and include it in the provider array on BookmarkManager by simply setting the array.

The bookmark manager will then start calling out to your bookmark provider depending on its position on the list. The topmost bookmark provider will get priority over the next one, and so on. This is important for actions that manipulate the bookmark provider. Whenever the bookmark manager receives a call to addBookmark(_:) or removeBookmark(_:), it reaches out to its providers, starting with the first one in the array. The provider that feels responsible for consuming this call should return true. At this point, additional providers will not be called. For addBookmark(_:), a bookmark provider could, for example, only take bookmarks with a certain type of action and return false for others, so that these are then forwarded to the next bookmark provider in the list. When removeBookmark(_:) is called, the provider should simply check if it owns this bookmark. If it does, it should remove it from its list and return true. If it doesn’t, it should return false and give the next bookmark provider a chance to make the same check.

Persisting Bookmarks

A bookmark provider is responsible for loading and saving bookmarks. The bookmark manager will call save on a provider whenever a provider should persist its current state. Loading should be done by the provider during initialization or whenever any of the methods from BookmarkProvider are called for the first time.

To properly persist a bookmark, it’s important that all of its properties are persisted. The preferred way to do this is to use Bookmark’s support for NSCoding, as this will also ensure that migration is done correctly in case new properties are introduced in the future. This can easily be achieved by using NSKeyedArchiver and NSKeyedUnarchiver.

If this isn’t suitable for your needs, you need to store the name and the action property, as well as the identifier and sortKey properties. The latter two are available only for this purpose, and their values should never be altered or interpreted in any way. Make sure to check the changelog for changes in Bookmark to make sure that you also store new properties that may be needed in the future.

If you only want to store go-to-based bookmarks, you can also — instead of storing the complete action — just store the page index of that action and recreate the action when loading the bookmarks. In this case, make sure to keep the default provider in the list of providers to hand other bookmarks off to it if you still want to support them.

For more information, check out CustomBookmarkProviderExample in the Catalog, which implements this case and stores the bookmarks in a CSV file.