Q: SwiftUI UndoManager — how does it work and isn't it working? 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 wrong
 import 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
             }
         })

     }
     }
 }


A: Just stumbled on this while looking for something else, and I thought I'd share my own experience. In my application, I was tearing my hair out about the sometimes presence of an UndoManager and my debugging revealed that over the lifespan of a given View, the undo manager could change. I store the UndoManager in a global state object so that everything the app does can (potentially) be registered for undo. So now I wind up with code like this:
struct MyView: View {
  @Environment(\.undoManager) var undoManager
  @ObservedObject var applicationStateModel ...
...
  var body: some View {
    VStack {
...
    }
    .onChange(of: undoManager) { newManager in
      applicationStateModel.undoManager = newManager
    }
    .onAppear {
      applicationStateModel.undoManager = undoManager
    }
  }
}

