23
May
2023
Undo/Redo with SwiftUI TextField
Reading time: 1 min
This recipe shows how to implement undo and redo with SwiftUI TextField.
The end result looks like this:
This solution works for SwiftUI 2+ (iOS 14+, macOS 11+).
Here's the full recipe:
- You can access the view's UndoManager from the environment, using the
undoManager
key. - The
UndoManager
'sregisterUndo
method requires a target that needs to be a class, so we'll use a separate@StateObject
to hold this info. If you're following MVVM, the View's ViewModel is a perfect place for that. - In the
TextField
itself, pass a custom Binding that interacts with theUndoManager
when the text is changed.
Check out the code for more details:
class ViewModel: ObservableObject {
@Published var text = ""
func registerUndo(_ newValue: String, in undoManager: UndoManager?) {
let oldValue = text
undoManager?.registerUndo(withTarget: self) { [weak undoManager] target in
target.text = oldValue // registers an undo operation to revert to old text
target.registerUndo(oldValue, in: undoManager) // this makes redo possible
}
text = newValue // update the actual value
}
}
struct UndoTest: View {
@Environment(\.undoManager) var undoManager
@StateObject private var model = ViewModel()
var body: some View {
VStack(spacing: 20) {
TextField("Undo/redo test", text: Binding<String>(
get: {
model.text // retrieve the value
}, set: {
model.registerUndo($0, in: undoManager) // set the value
}))
Button("Undo") {
undoManager?.undo()
}
.disabled(undoManager?.canUndo == false)
Button("Redo") {
undoManager?.redo()
}
.disabled(undoManager?.canRedo == false)
}
.padding()
}
}