PSPDFKit 10.4 Migration Guide
This guide covers updating an iOS or Mac Catalyst project from PSPDFKit 10.3 for iOS to PSPDFKit 10.4 for iOS. We encourage you to update as soon as possible, in order to take advantage of future new features and fixes.
Cache Initializers
In PSPDFKit 10.4 for iOS, we removed public APIs for initializing PDFCache
, DiskCache
, and MemoryCache
because PSPDFKit handles creating these objects internally, and there shouldn’t be a need to initialize them.
To access the globally used cache, you can use SDK.shared.cache
.
New Undo and Redo Architecture
With PSPDFKit 10.4 for iOS, registering and performing undo and redo operations in Document
has been completely revamped to be more powerful, performant, and customizable.
Recording Commands Is Now Opt-In
The most important feature of the new undo and redo mechanism is that it’s now opt-in. Undoable commands will no longer be picked up automatically based on key-value observing. Instead, they need to be explicitly recorded, most often as a result of a user action.
PSPDFKit 10.4 for iOS adds support for recording undoable commands for adding, removing, and changing annotations. Unless registering actions is disabled in the undo manager, PSPDFKit will record these as a result of most user interactions through the built-in UI.
Adding and Removing Annotations
To record an undoable command of adding one or more annotations to a document, you can wrap your code in the following way:
document.undoController.recordCommand(named: "Insert Note", adding: [note]) {
document.add(annotations: [note])
}
Similarly, to record an undoable command of removing one or more annotations from a document, you can write the following:
document.undoController.recordCommand(named: "Remove Shapes", removing: [square, circle]) {
document.remove(annotations: [square, circle])
}
The add(annotations:options:)
and remove(annotations:options:)
calls can be replaced with equivalent calls to AnnotationManager
or AnnotationProvider
, as long as such a call results in an annotation being added to or removed from a document.
Note that adding and removing annotations using AnnotationStateManager
will result in undoable commands being automatically recorded.
Changing Annotations
To record an undoable command of changing properties of one or more annotations, you can write:
document.undoController.recordCommand(named: "Increase Font Size", changing: [freeText]) { freeText.fontSize += 10 freeText.sizeToFit() }
On a technical level, the new undo and redo architecture will perform a diff of properties affected by the execution of the given scope
closure and append an appropriate undoable command to the UndoManager
’s stack.
Migration Cases
This section explores various use cases of the old undo and redo architecture, and it describes how to achieve the same results using the new API.
Disabling Undo and Redo
Use the UndoManager
directly to call disableUndoRegistration()
. Note that disabling an undo manager is a balancing operation, meaning enableUndoRegistration()
must be called an equal number of times to reenable it:
// Previously you would have... document.isUndoEnabled = false
// Now you can write...
document.undoController.undoManager.disableUndoRegistration()
Performing Non-Undoable Operations
Since undoable commands are no longer automatically recorded, you can perform non-undoable operations by not asking UndoController
to record them:
// Previously you would have... UndoController.performWithoutUndo(undoController: document.undoController) { line.fillColor = .red line.alpha = 0.5 )
// Now you can write... line.fillColor = .red line.alpha = 0.5
Grouping Multiple Actions Together
Use UndoController
to record undoable commands with multiple actions:
// Previously you would have... UndoController.performAsGroup( undoController: document.undoController, closure: { firstFreeText.contents += secondFreeText.contents document.remove(annotations: [firstFreeText]) }, name: "Merge Contents" )
// Now you can write... document.undoController.recordCommand(named: "Merge Contents") { recorder in recorder.record(changing: [firstFreeText]) { firstFreeText.contents += secondFreeText.contents } recorder.record(removing: [secondFreeText]) { document.remove(annotations: [secondFreeText]) } }
Performing Undo and Redo
Use the UndoManager
directly to check if undoing or redoing is possible, and if so, do it:
// Previously you would have... if document.undoController.canUndo { document.undoController.undo() } if document.undoController.canRedo { document.undoController.redo() }
// Now you can write... if document.undoController.undoManager.canUndo { document.undoController.undoManager.undo() } if document.undoController.undoManager.canRedo { document.undoController.undoManager.redo() }
Reacting to State Changes
Previously, you’d use UndoController.didAddUndoAction
and UndoController.didRemoveUndoAction
notifications to react to the state changes inside an undo controller. These notifications have been removed.
Implementing the annotationStateManager(_:didChangeUndoState:redoState:)
delegate method of AnnotationStateManager
is now the recommended way to ensure your custom UI stays up to date with the state of the undo manager.
You can also observe various undo manager notifications if you need more detailed control. In particular, an NSUndoManagerDidCloseUndoGroup
notification will be posted whenever a new undoable command is recorded.
Custom Annotation Subclasses
If you define custom annotation subclasses, previously you had to override UndoSupport
protocol requirements — most importantly, the keysForValuesToObserveForUndo()
function — to inform the undo and redo architecture about any new properties that should be taken into consideration when observing changes made to an annotation. This protocol has been removed, and the same can now be achieved by overriding the propertyKeysForUndo
property:
// Previously you would have... override static func keysForValuesToObserveForUndo() -> Set<String> { super.keysForValuesToObserveForUndo().union([ "addedProperty", ]) }
// Now you can write... override static var propertyKeysForUndo: Set<String> { super.propertyKeysForUndo.union([ "addedProperty", ]) }
Further Reading
For more information about the new undo and redo architecture, check out our undo and redo guide and the documentation of UndoController
.
Breaking API Changes
- Removed
UndoController
Use the new protocol with the same name instead. - Removed
UndoSupport
See the Custom Annotation Subclasses section. - Removed
Document.isUndoEnabled
See the Disabling Undo and Redo section. - Removed
UndoController.didAddUndoAction
See the Reacting to State Changes section. - Removed
UndoController.didRemoveUndoAction
See the Reacting to State Changes section. - Removed
PDFContainerAnnotationProvider.undoController
Use theDocument.undoController
property instead. - Removed
PDFContainerAnnotationProvider.registerAnnotations(forUndo:)
Annotations no longer need to be registered for KVO. - Removed
PDFCache.init(settings:)
See the Cache Initializers section. - Removed
MemoryCache.init(settings:)
See the Cache Initializers section. - Removed
DiskCache.init(cacheDirectory:fileFormat:settings:)
See the Cache Initializers section.