从SwiftUI环境捕获UndoManager

从SwiftUI环境捕获UndoManager,swiftui,Swiftui,我希望能够从文档模型内访问UndoManager,以便可以从模型内注册撤消操作: // Assume I've extended MyDocument to conform to ReferenceFileDocument elsewhere... final class MyDocument { private var undoManager: UndoManager? @Published var aNumber = 5 { willSet {

我希望能够从文档模型内访问UndoManager,以便可以从模型内注册撤消操作:

// Assume I've extended MyDocument to conform to ReferenceFileDocument elsewhere...
final class MyDocument {
    private var undoManager: UndoManager?

    @Published var aNumber = 5 {
        willSet {
            if let undoManager = undoManager {
                let currentValue = self.aNumber
                undoManager.registerUndo(withTarget: self) { target in
                    target.aNumber = currentValue
                }
            }
        }
    }

    func setUndoManager(undoManager: UndoManager?) {
        self.undoManager = undoManager
    }
}
为了注册undoManager,我尝试了以下方法:

struct DocumentView: View {
    let document : MyDocument
    @Environment(\.undoManager) var undoManager
    
    var body: some View {
        MyDocumentEditor(document: document)
        .onAppear {
            document.setUndoManager(undoManager: undoManager)
        }
    }
}
运行我的应用程序并加载已保存的文档时,此操作有效。但当从新文档开始时,UndoManager为零

我试过这样的方法:

@Environment(\.undoManager) var undoManager { 
    didSet { 
        self.document.setUndoManager(undoManager: undoManager)
    }
}

我的目标是尽可能地在模型和视图中保持尽可能多的逻辑,只关注UI内容。我希望ReferenceFileDocument提供一个属性来访问其关联的UndoManager,就像NSDocument提供的那样。

我已经找到了一个解决方案-尽管它感觉不太正确。在视图的顶层,我将undoManager传递给我在文档上保留的属性:

struct ContentView: View {
    let document: MyDocument
    @Environment(\.undoManager) var undoManager

    var body: some View {
        document.setUndoManager(undoManager: undoManager)
        return TopLevelView(document: document)
    }
}

SwiftUI使用以下方法看起来更自然

var body: some View {
    TopLevelView(document: document, undoManager: undoManager)
}


经过一天多的努力,我的收获是
环境中的
UndoManager
是连接到视图所在的
NSWindow
的。我的解决办法是:

protocol Undoable {
   func inverted() -> Self 
}

class Store<State, Action : Undoable> {

   var state : State 
   var reducer : (inout State, Action) -> Void 

   //...init...

   func send(_ action: Action, undoManager: UndoManager) {//passed as an argument
      reducer(&state, action)
      undoManager.registerUndo(withTarget: self){target in 
         target.send(action.inverted())
      }
   }

   //...other methods...

}

(也许您需要将
调度程序
放入
状态对象
,我没有测试该部分,因为我很高兴在我的小应用程序中将撤销管理器作为函数参数传递给您)。

是的-感觉更好。但是,UndoManager不符合ObserveObject,因此其属性定义需要省略
@ObserveObject
,因此为
var UndoManager:UndoManager?
protocol Undoable {
   func inverted() -> Self 
}

class Store<State, Action : Undoable> {

   var state : State 
   var reducer : (inout State, Action) -> Void 

   //...init...

   func send(_ action: Action, undoManager: UndoManager) {//passed as an argument
      reducer(&state, action)
      undoManager.registerUndo(withTarget: self){target in 
         target.send(action.inverted())
      }
   }

   //...other methods...

}
class Dispatcher<State, Action : Undoable> : ObservableObject {

   let store : Store<State, Action> 
   let undoManager : UndoManager //see below
   //...init...

   func send(_ action: Action) {
      objectWillChange.send()
      store.send(action, undoManager: undoManager)
   }

}

struct ContentView<State, Action : Undoable> : View {

   @Environment(\.undoManager) var undoManager
   let document : Store<State, Action>

   var body : some View {
      ViewHierarchy().environmentObject(Dispatcher(store: document,
                                                   undoManager: undoManager)
   }

}