I'm trying to learn how UndoManager works. I made a small app to Undo/Redo something. And I have few questions, I cannot find answer in documentation:
-
I know UndoManager could be accessed in View via
@Environment(\.undoManager) var undoManager
Brilliant. But in this case it's only available in a View, if I want use it somewhere deeper in a structure I have to pass it via Model to Objects... Is a way to access the same UndoManager in other objects? Models, Data... I could be much more convenient, specially if there is many Undo groupings. If I create UndoManager in Document (or somewhere else) it's not visible for main menu Edit -> Undo, Redo
-
In the app repository on GitHub I implemented Undo/Redo. For me (haha) it looks OK and even works, but not for first action. First action Undo causes
Thread 1: signal SIGABRT
error. After three actions I can undo two last actions... Bang. Something is wrongimport Foundation import SwiftUI struct CustomView: View { @ObservedObject var model: PointsViewModel @Environment(\.undoManager) var undoManager @GestureState var isDragging: Bool = false @State var dragOffsetDelta = CGPoint.zero var formatter: NumberFormatter { let formatter = NumberFormatter() formatter.allowsFloats = true formatter.minimumFractionDigits = 2 formatter.maximumFractionDigits = 5 return formatter } var body: some View { HStack { VStack(alignment: .leading, spacing: 10) { ForEach(model.insideDoc.points.indices, id:\.self) { index in HStack { TextField("X", value: $model.insideDoc.points[index].x, formatter: formatter) .frame(width: 80, alignment: .topLeading) TextField("Y", value: $model.insideDoc.points[index].y, formatter: formatter) .frame(width: 80, alignment: .topLeading) Spacer() } } Spacer() } ZStack { ForEach(model.insideDoc.points.indices, id:\.self) { index in Circle() .foregroundColor(index == model.selectionIndex ? .red : .blue) .frame(width: 20, height: 20, alignment: .center) .position(model.insideDoc.points[index]) //MARK: - drag point .gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local) .onChanged { drag in if !isDragging { dragOffsetDelta = drag.location - model.insideDoc.points[index] model.selectionIndex = index let now = model.insideDoc.points[index] undoManager?.registerUndo(withTarget: model, handler: { model in model.insideDoc.points[index] = now model.objectWillChange.send() }) undoManager?.setActionName("undo Drag") } model.insideDoc.points[index] = drag.location - dragOffsetDelta } .updating($isDragging, body: { drag, state, trans in state = true model.objectWillChange.send() }) .onEnded({drag in model.selectionIndex = index model.insideDoc.points[index] = drag.location - dragOffsetDelta model.objectWillChange.send() }) ) } }.background(Color.orange.opacity(0.5)) //MARK: - new point .gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local) .onEnded{ loc in let previousIndex = model.selectionIndex undoManager?.registerUndo(withTarget: model, handler: {model in model.insideDoc.points.removeLast() model.selectionIndex = previousIndex model.objectWillChange.send() }) model.insideDoc.points.append(loc.location) model.selectionIndex = model.insideDoc.points.count - 1 model.objectWillChange.send() } ) //MARK: - delete point .onReceive(deleteSelectedObject, perform: { _ in if let deleteIndex = model.selectionIndex { let deleted = model.insideDoc.points[deleteIndex] undoManager?.registerUndo(withTarget: model, handler: {model in model.insideDoc.points.insert(deleted, at: deleteIndex) model.objectWillChange.send() }) undoManager?.setActionName("remove Point") model.insideDoc.points.remove(at: deleteIndex) model.objectWillChange.send() model.selectionIndex = nil } }) } } }
Any comments about quality of my algorithms will be highly appreciated.