How to add support for undo and redo
How to add support for undo and redo êŽë š
Updated for Xcode 15
It is startlingly easy to add undo and redo support to SwiftData, and really it takes just two steps: enabling undo, then calling undo either through the SwiftUI environment or through your model contextâs own undo manager.
First, adjust the way you create your model container so that you enable undo. If youâre using SwiftUI, this probably means changing your modelContainer()
modifier to something like this:
.modelContainer(for: [Store.self, Book.self], isUndoEnabled: true)
Once you have your undo manager in place, you can call undo()
or redo()
on it and SwiftData will automatically undo the last set of changes. For example, if you were using SwiftUI youâd first request the environmentâs undo manager, like this:
@Environment(\.undoManager) var undoManager
And then trigger an undo action like this:
undoManager?.undo()
If youâre creating your model container manually, you need to create the undo manager manually too:
container = try ModelContainer(for: Store.self, Book.self)
container.mainContext.undoManager = UndoManager()
And then you should use modelContext.undoManager?.undo()
to perform an undo.
Tips
Calling undo()
or redo()
undoes or redoes the last set of changes you made, but what âlast set of changesâ means is a bit fuzzy. What I can say for sure is that it doesnât mean individual changes (if you make several changes at once, they will all be undone), and it also doesnât mean all the changes made since a save last happened (if you make a change, call save()
, then make another change, both will be undone). Instead, it seems to be runloop-based: All changes made in the current runloop are grouped together into a single undo/redo batch, even if you attempt to start a fresh transaction inside the runloop.
You can also call the undo manager directly on any model context, although this is likely to cause problems if you have other undoable operations on your view.
You can also redo actions, like so:
undoManager?.redo()
Important
Iâve found that undo works perfectly every time, correctly undoing property changes or removing objects and relations that were inserted. However, Iâve found redo more flaky: it seems happy to redo simple property changes (changing someoneâs name, for example), but struggles when redoing changes that involved objects with relations being deleted â Iâve had it crash several times. Tread carefully!
When undo/redo support is working correctly, you should automatically get three-finger swipe support working too: three-finger swipe left for undo, or right for redo.
As well as calling undo()
and redo()
, you can also check your undo manager's canUndo
and canRedo
property, to determine whether the actions are possible in the first place.