SwiftData is a framework that is built on top of Core Data and greatly simplifies working with data in your SwiftUI app. One caveat is that not all functionality of Core Data can be found in SwiftData. In this turorial we’ll look at the UndoManager that tracks changes and allows you to undo and redo changes you have made. This can be extremely useful for apps that implement SwiftData. SwiftData makes this process extremely simple to configure.
Lets begin with the app created in the ToDo List app with SwiftData. You can download this below.
Make any changes needed to the bundle ID and signing in the target as well as the container you want to use for storing the data.
.modelContainer(for: ToDoItem.self, isUndoEnabled: true)
In ToDoApp, add isUndoEnabled: true to the modelContainer. When not declared it defaults to false.
At this point, thanks to SwiftData and behind the scenes, you now have undo available in your ToDo app. To access it, make a change and then swipe left or right with three fingers to step through the changes. Swiping one way will undo changes, and swiping the other way will redo the changes.
One thing to note is that changes are only tracked on a single device. If you mark a task as complete on the iPad and it syncs across, you can’t undo that change on an iPhone with the swipe gestures.
What Else can Undo Do?
It seems a little odd to write a turorial, give you one parameter to add and then say we’re done, so lets take a look at how you can implement more to make it useful for you and your users given that many might not realise a three finger swipe would be needed to undo or redo an action, let alone know if undo is even implemented.
The undoManager @Environment variable has a number of properties that can be queried as well as methods that can be called. Some of these include the likes of canUndo which is a Bool that tells you if there are any actions that can be undone. Likewise, canRedo works the same and lets you know if you can redo a change that you just used undo on.
@Environment(\.undoManager) private var undoManager
In ContentView add the undoManager from the @Environment. The undoManager here is kept up to date when you set isUndoEnabled to true earlier.
Next, update the ContentView with the following under the ToolBarItem:
ToolbarItemGroup(placement: .bottomBar) {
Spacer()
Button(action : {
undoManager?.undo()
}) {
Image(systemName: "arrow.uturn.backward")
}
.disabled(!(undoManager?.canUndo ?? false))
Button(action : {
undoManager?.redo()
}) {
Image(systemName: "arrow.uturn.forward")
}
.disabled(!(undoManager?.canRedo ?? false))
}
This adds buttons to the bottom of the NavigationView with the spacer on line 2 pushing the buttons to the right of the view.
We add in two Button views here that manually call the undo() or redo() methods in the undoManager. This is the equivalent of using the swipe gesture I mentioned earlier.
We add an Image from SF Symbols and under each button we check if the undoManaget an undo or redo. If it can, the button is enabled.
If you complete tasks now, you will see the undo arrow become enabled. If you tap it, the redo arrow enables and the undo disables.
Other methods include undoCount which is a method that returns an Int. If your UI accounted for this, you can put the number of times you can invoke the undo method. Likewise, redoCount tells you the number of times you can invoke the redo method.
There are more features available such as making groups of changes, similar to transactions in SQL, where you can group changes together and undo a whole group together.
A full list of methods and properties can be found in the UndoManager class documentation.
If you have any questions on any part of this, please do not hesitate to reach out below.
Leave a Reply
You must be logged in to post a comment.