Undo and Redo

PSPDFKit for iOS supports undo and redo for creating and editing annotations, including form filling. Users can undo and redo changes using the buttons in PSPDFAnnotationToolbar.

To achieve this, the SDK uses a standard component named NSUndoManager. If you have never used it before, we recommend studying both the Apple Documentation and the NSHipster article about this topic to understand the principles of how this class works.

PSPDFKit uses a PSPDFUndoController object that wraps and manages the undo manager and allows more convenient access to properties such as canUndo.

The undo controller also extends Apple’s concept by adding a coalescing mode, which merges changes happening in quick succession into a single undo event. This is used in various places — for example, when a color is chosen from the color picker, we don’t save every single change. Rather we commit the change after a short period of time to provide a better undo experience for the user. This implies that whenever the PSPDFUndoController object exposes properties, you should use them, and only in cases where there’s no other way to go down to the wrapped undo manager instance.

The PSPDFUndoController instance is exposed via the undoController property on PSPDFDocument. Undo can also be disabled on a per-document level via the undoEnabled property.

Listening for Undo and Redo Changes

If you’re implementing your own undo and redo buttons and are trying to update the enabled state, you need to listen for the following notifications:

  • NSUndoManagerDidUndoChangeNotification
  • NSUndoManagerDidRedoChangeNotification
  • PSPDFUndoControllerAddedUndoActionNotification
  • PSPDFUndoControllerRemovedUndoActionNotification

For convenience reasons, we also expose a delegate method on PSPDFAnnotationStateManager that listens to the set above and calls back with the undo- and redo-enabled states:

optional public func annotationStateManager(_ manager: PSPDFAnnotationStateManager, didChangeUndoState undoEnabled: Bool, redoState redoEnabled: Bool)
- (void)annotationStateManager:(PSPDFAnnotationStateManager *)manager didChangeUndoState:(BOOL)undoEnabled redoState:(BOOL)redoEnabled;

Note that this class supports multiple delegates. Use addDelegate: and removeDelegate: to register them.

See PSCVerticalAnnotationToolbarExample.m for a sample implementation of custom undo and redo buttons.

Shake to Undo

PSPDFKit is a good iOS citizen and supports the native ways of undoing, which on the iPhone is Shake to Undo — see the Undo and Redo section in the iOS Human Interface Guidelines for more on this. Shake to Undo can be deactivated by the user in the phone Accessibility settings or via the applicationSupportsShakeToEdit property on UIApplication.

Implementation Notes

Saving annotations will clear the undo and redo stacks. As a result, writing objects into the PDF changes various details, so providing a reliable undo is difficult. Saving happens at various points, including when the application enters the background.

On a technical level, our undo works via KVO and registering each annotation object and tracking changes on a property basis.