PDF Document Features

PSPDFKit 7.4 for iOS introduced a new document features system. Its goals are to allow you to check if certain features are available, to get notified of changes, and to easily customize the availability of features yourself.

Checking Features

PDFDocumentFeatures is the class you can access to check if a certain feature is currently available. PDFDocumentFeatures is owned by Document and represents the availability of the document owning it. You can access the features through the features property of Document. The available features are declared in PDFDocumentFeaturesSource, a protocol containing a method for every feature defined in PSPDFKit. While the methods in this protocol are optional, PDFDocumentFeatures guarantees to implement all of them so you can safely access them without checking their availability first.

Observing Features

When building UI components or other components that rely on the state of a feature, it is helpful to be notified when the state of a document changes. For example, a document can be unmodifiable while it is still being loaded, but later on it may allow modifications. When these state changes happen, PDFDocumentFeatures notifies its observers.

Registering an observer is easy. Once registered, you get back an observer token, which you can then use to either make the observer bound to the lifetime of another object (i.e. let the observer automatically remove itself when the other object is deallocated) or explicitly unregister the observer:

let observer = document.features.addObserver(forFeature: #selector(getter: PDFDocumentFeaturesSource.canEditBookmarks)) { [weak self] (value) in
    // Update UI.
}

// Make the observer unregister automatically as soon as `self` is deallocated.
observer.bind(toObjectLifetime: self)
id<PSPDFDocumentFeaturesObserver> observer = [document.features addObserverForFeature:PSPDFDocumentFeatureMake(canEditBookmarks) updateHandler:^(BOOL value) {
    // Update UI.
}];

// Make the observer unregister automatically as soon as `self` is deallocated.
[observer bindToObjectLifetime:self];

If you instead want to explicitly unregister the observer, you should hold on to the observer and call remove(_:) on the document features instance you used to register the observer.

Make certain not to create any retain cycles if you remove your observer, whether automatically or manually, during deallocation of objects that are referenced in the update block.

Customizing Features

The features system also allows you to customize the availability of certain features. This is done by implementing your own PDFDocumentFeaturesSource. To customize features, simply make a class conform to PDFDocumentFeaturesSource. There is no need to implement all methods; you should only implement the ones you want to customize.

Note that the different sources (our internal ones and your custom ones) will all be evaluated, and the result will be combined through a logical AND, meaning you can only disable features that would otherwise be enabled. You cannot enable features that are disabled by other sources, because a disabled feature is usually the result of either a system limitation such as printing not being available on the current device, or the document that should store changes not being in a writable location. As soon as one source marks a feature as unavailable, the feature will not be available and its UI elements will be disabled by PSPDFKit.

After implementing your custom source, you need to add it to each document features instance. Note that each instance can only be in one source; you cannot reuse your source instances in multiple documents:

let source = MySource()
document.features.add([source])
id<PSPDFDocumentFeaturesSource> source = [MySource new];
[document.features addSources:@[source]];

PDFDocumentFeatures will cache the result of the sources. Therefore, it is important that you notify it about changes in your source. For this purpose, PDFDocumentFeaturesSource has one required property: the back reference to the features object it is attached to. You can use this to call update() on it. This will let the document features object know to check its sources again and report any changes to registered observers. One way to do this is to call update() whenever the setter of one of your properties is changed. Of course, this depends on how your source behaves internally:

var canEditBookmarks: Bool {
    didSet {
        features?.update()
    }
}
- (void)setCanEditBookmarks:(BOOL)canEditBookmarks {
    _canEditBookmarks = canEditBookmarks;
    [self.features updateFeatures];
}

See DisableBookmarkEditingExample.swift in PSPDFKit Catalog for more details.