---
title: "PSPDFKit 10.4 Migration Guide"
canonical_url: "https://www.nutrient.io/guides/ios/migration-guides/pspdfkit-10-4-migration-guide/"
md_url: "https://www.nutrient.io/guides/ios/migration-guides/pspdfkit-10-4-migration-guide.md"
last_updated: "2026-05-14T16:53:43.872Z"
description: "Discover how to update your iOS project to PSPDFKit 10.4, including new cache management and enhanced undo/redo functionality for improved performance."
---

This guide covers updating an iOS or Mac Catalyst project from PSPDFKit&nbsp;10.3 for iOS to PSPDFKit&nbsp;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`](https://www.nutrient.io/api/ios/documentation/pspdfkit/pdfcache), [`DiskCache`](https://www.nutrient.io/api/ios/documentation/pspdfkit/diskcache), and [`MemoryCache`](https://www.nutrient.io/api/ios/documentation/pspdfkit/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`](https://www.nutrient.io/api/ios/documentation/pspdfkit/sdk/cache).

## New Undo and Redo Architecture

With PSPDFKit 10.4 for iOS, registering and performing undo and redo operations in [`Document`](https://www.nutrient.io/api/ios/documentation/pspdfkit/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](https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift). 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:

```swift

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:

```swift

document.undoController.recordCommand(named: "Remove Shapes", removing: [square, circle]) {
    document.remove(annotations: [square, circle])
}

```

The [`add(annotations:options:)`](https://www.nutrient.io/api/ios/documentation/pspdfkit/document/add(annotations:options:)) and [`remove(annotations:options:)`](https://www.nutrient.io/api/ios/documentation/pspdfkit/document/remove(annotations:options:)) calls can be replaced with equivalent calls to [`AnnotationManager`](https://www.nutrient.io/api/ios/documentation/pspdfkit/annotationmanager) or [`AnnotationProvider`](https://www.nutrient.io/api/ios/documentation/pspdfkit/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`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/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:

```swift

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`](https://developer.apple.com/documentation/foundation/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`](https://developer.apple.com/documentation/foundation/undomanager) directly to call [`disableUndoRegistration()`](https://developer.apple.com/documentation/foundation/undomanager/1412239-disableundoregistration). Note that disabling an undo manager is a balancing operation, meaning [`enableUndoRegistration()`](https://developer.apple.com/documentation/foundation/undomanager/1408957-enableundoregistration) must be called an equal number of times to reenable it:

```swift

// Previously you would have...
document.isUndoEnabled = false

```

```swift

// 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`](https://www.nutrient.io/api/ios/documentation/pspdfkit/undocontroller) to record them:

```swift

// Previously you would have...
UndoController.performWithoutUndo(undoController: document.undoController) {
    line.fillColor =.red
    line.alpha = 0.5
)

```

```swift

// Now you can write...
line.fillColor =.red
line.alpha = 0.5

```

### Grouping Multiple Actions Together

Use [`UndoController`](https://www.nutrient.io/api/ios/documentation/pspdfkit/undocontroller) to record undoable commands with multiple actions:

```swift

// Previously you would have...
UndoController.performAsGroup(
    undoController: document.undoController,
    closure: {
        firstFreeText.contents += secondFreeText.contents
        document.remove(annotations: [firstFreeText])
    },
    name: "Merge Contents"
)

```

```swift

// 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`](https://developer.apple.com/documentation/foundation/undomanager) directly to check if undoing or redoing is possible, and if so, do it:

```swift

// Previously you would have...
if document.undoController.canUndo {
    document.undoController.undo()
}
if document.undoController.canRedo {
    document.undoController.redo()
}

```

```swift

// 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:)`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/annotationstatemanagerdelegate/annotationstatemanager(_:didchangeundostate:redostate:)) delegate method of [`AnnotationStateManager`](https://www.nutrient.io/api/ios/documentation/pspdfkitui/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](https://developer.apple.com/documentation/foundation/undomanager) if you need more detailed control. In particular, an [`NSUndoManagerDidCloseUndoGroup`](https://developer.apple.com/documentation/foundation/nsnotification/name/1408817-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`](https://www.nutrient.io/api/ios/documentation/pspdfkit/annotation/propertykeysforundo) property:

```swift

// Previously you would have...
override static func keysForValuesToObserveForUndo() -> Set<String> {
    super.keysForValuesToObserveForUndo().union([
        "addedProperty",
    ])
}

```

```swift

// 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](https://www.nutrient.io/guides/ios/features/undo-redo.md) guide and the documentation of [`UndoController`](https://www.nutrient.io/api/ios/documentation/pspdfkit/undocontroller).

## Breaking API Changes

- Removed: `UndoController`

  _Use the new protocol [with the same name](https://www.nutrient.io/api/ios/documentation/pspdfkit/undocontroller) instead._

- Removed: `UndoSupport`

  _See the [Custom Annotation Subclasses](#custom-annotation-subclasses) section._

- Removed: `Document.isUndoEnabled`

  _See the [Disabling Undo and Redo](#disabling-undo-and-redo) section._

- Removed: `UndoController.didAddUndoAction`

  _See the [Reacting to State Changes](#reacting-to-state-changes) section._

- Removed: `UndoController.didRemoveUndoAction`

  _See the [Reacting to State Changes](#reacting-to-state-changes) section._

- Removed: `PDFContainerAnnotationProvider.undoController`

  _Use the [`Document.undoController`](https://www.nutrient.io/api/ios/documentation/pspdfkit/document/undocontroller) property instead._

- Removed: `PDFContainerAnnotationProvider.registerAnnotations(forUndo:)`

  _Annotations no longer need to be registered for KVO._

- Removed: `PDFCache.init(settings:)`

  _See the [Cache Initializers](#cache-initializers) section._

- Removed: `MemoryCache.init(settings:)`

  _See the [Cache Initializers](#cache-initializers) section._

- Removed: `DiskCache.init(cacheDirectory:fileFormat:settings:)`

  _See the [Cache Initializers](#cache-initializers) section._
---

## Related pages

- [14 9 Migration Guide](/guides/ios/migration-guides/14-9-migration-guide.md)
- [14 2 Migration Guide](/guides/ios/migration-guides/14-2-migration-guide.md)
- [Migrating From Apple Pdfkit](/guides/ios/migration-guides/migrating-from-apple-pdfkit.md)
- [Migrate to electronic signatures](/guides/ios/migration-guides/migrating-to-electronic-signatures.md)
- [Pspdfkit 10 3 Migration Guide](/guides/ios/migration-guides/pspdfkit-10-3-migration-guide.md)
- [Pspdfkit 11 5 Migration Guide](/guides/ios/migration-guides/pspdfkit-11-5-migration-guide.md)
- [Pspdfkit 10 Migration Guide](/guides/ios/migration-guides/pspdfkit-10-migration-guide.md)
- [Migrating To Advanced Digital Signatures Api](/guides/ios/migration-guides/migrating-to-advanced-digital-signatures-api.md)
- [Pspdfkit 11 3 Migration Guide](/guides/ios/migration-guides/pspdfkit-11-3-migration-guide.md)
- [Pspdfkit 13 3 Migration Guide](/guides/ios/migration-guides/pspdfkit-13-3-migration-guide.md)
- [Pspdfkit 13 Migration Guide](/guides/ios/migration-guides/pspdfkit-13-migration-guide.md)
- [Pspdfkit 12 2 Migration Guide](/guides/ios/migration-guides/pspdfkit-12-2-migration-guide.md)
- [Pspdfkit 12 3 Migration Guide](/guides/ios/migration-guides/pspdfkit-12-3-migration-guide.md)
- [Pspdfkit 3 Migration Guide](/guides/ios/migration-guides/pspdfkit-3-migration-guide.md)
- [Pspdfkit 11 4 Migration Guide](/guides/ios/migration-guides/pspdfkit-11-4-migration-guide.md)
- [Pspdfkit 4 Migration Guide](/guides/ios/migration-guides/pspdfkit-4-migration-guide.md)
- [Pspdfkit 5 Migration Guide](/guides/ios/migration-guides/pspdfkit-5-migration-guide.md)
- [Pspdfkit 6 5 Migration Guide](/guides/ios/migration-guides/pspdfkit-6-5-migration-guide.md)
- [Pspdfkit 12 Migration Guide](/guides/ios/migration-guides/pspdfkit-12-migration-guide.md)
- [Pspdfkit 6 Migration Guide](/guides/ios/migration-guides/pspdfkit-6-migration-guide.md)
- [Pspdfkit 9 2 Migration Guide](/guides/ios/migration-guides/pspdfkit-9-2-migration-guide.md)
- [Pspdfkit 7 6 Migration Guide](/guides/ios/migration-guides/pspdfkit-7-6-migration-guide.md)
- [Pspdfkit 9 4 Migration Guide](/guides/ios/migration-guides/pspdfkit-9-4-migration-guide.md)
- [Pspdfkit 9 5 Migration Guide](/guides/ios/migration-guides/pspdfkit-9-5-migration-guide.md)
- [Pspdfkit 9 3 Migration Guide](/guides/ios/migration-guides/pspdfkit-9-3-migration-guide.md)
- [Upgrading](/guides/ios/getting-started/upgrading.md)
- [Pspdfkit 9 Migration Guide](/guides/ios/migration-guides/pspdfkit-9-migration-guide.md)
- [Migrate to PSPDFKit 7 with ease](/guides/ios/migration-guides/pspdfkit-7-migration-guide.md)
- [PSPDFKit 8 migration guide for iOS developers](/guides/ios/migration-guides/pspdfkit-8-migration-guide.md)

