I am playing with SwiftData in an iOS app using XCode 15 Beta 5. Really enjoying the experience so far, very easy to use. My question relates to how can I enable undo and redo functionality when editing an existing entity in a user entry form. The functionality I am hoping to achieve is after the user has made one or more changes to the managed object data, s/he should be able to press the undo button once to undo each input control to its original value in reverse order of the changes made. Likewise redo should restore the last undone value.
Here is the code for the user entry form:
import SwiftUI
import SwiftData
struct TradingEntityEditor: View {
@Environment(\.modelContext) private var context
@Environment(\.dismiss) private var dismiss
@Bindable var tradingEntity: TradingEntity
@State private var autoSaveOnAppear = false
var canUndo: Bool { context.undoManager?.canUndo ?? true }
var canRedo: Bool { context.undoManager?.canRedo ?? true }
var body: some View {
VStack(spacing: 12) {
LabeledContent("Name") {
TextField("required", text: $tradingEntity.name)
.textFieldStyle(.roundedBorder)
}
LabeledContent("Address") {
TextField("", text: $tradingEntity.address)
.textFieldStyle(.roundedBorder)
}
LabeledContent("Email") {
TextField("email address", text: $tradingEntity.email)
.textFieldStyle(.roundedBorder)
}
LabeledContent("Telehone") {
TextField("contact phone number", text: $tradingEntity.telephone)
.textFieldStyle(.roundedBorder)
}
Spacer()
}
.labeledContentStyle(TextFieldLabeledContentStyle())
.padding()
.toolbar{
ToolbarItem(placement: .topBarTrailing) {
Button {
// Perform undo last change
undoLastChange()
} label: {
Label("undo changes", systemImage: "arrow.uturn.backward")
}
.disabled(!canUndo)
}
ToolbarItem(placement: .topBarTrailing) {
Button {
// Perform redo last undo
redoLastUndo()
} label: {
Label("undo changes", systemImage: "arrow.uturn.forward")
}
.disabled(!canRedo)
}
}
.navigationTitle("Edit trading entity")
.onAppear {
appearAction()
}
.onDisappear {
disappearAction()
}
}
func appearAction() {
UITextField.appearance().clearButtonMode = .whileEditing
autoSaveOnAppear = context.autosaveEnabled
context.autosaveEnabled = false
}
func disappearAction() {
if context.hasChanges {
do {
try context.save()
} catch {
print("Failed to save changes to current data")
}
}
context.autosaveEnabled = autoSaveOnAppear
}
func undoLastChange() {
guard let um = context.undoManager, um.canUndo else { return }
um.undo()
}
func redoLastUndo() {
guard let um = context.undoManager, um.canRedo else { return }
um.redo()
}
}
The undo operation doesn't perform the expected changes although some typed characters are removed from the last field. If anyone can suggest how this can be implemented, I would be most grateful and I am sure the answer will be useful to lots of developers.
EDIT For clarity, the functionality appears to be already available out-of-the-box by selecting the field to undo and shaking the device, 3-finger swipe (iPhone) or on-screen keyboard undo/redo button. The shake action also pops up a confirmation dialog. I want to implement a single button click which selects the last field edited and restores the original value.