#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 {
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)
}
}
Моя цель здесь — попытаться сохранить как можно больше логики в модели и представлениях, сосредоточив внимание только на элементах пользовательского интерфейса, насколько это возможно. Я хотел бы, чтобы ReferenceFileDocument предоставил свойство для доступа к связанному с ним UndoManager, которое доступно с NSDocument.
Ответ №1:
Для SwiftUI выглядит более естественным использовать следующий подход
var body: some View {
TopLevelView(document: document, undoManager: undoManager)
}
и
struct TopLevelView: View {
@ObservedObject var document : MyDocument
var undoManager: UndoManager?
init(document: MyDocument, undoManager: UndoManager?) {
self.document = document
self.undoManager = undoManager
self.setUndoManager(undoManager: undoManager)
}
// ... other code
}
Комментарии:
1. Да, так кажется лучше. Однако UndoManager не соответствует ObservableObject, поэтому его определение свойств необходимо опустить
@ObservedObject
и, следовательно, бытьvar undoManager: UndoManager?
Ответ №2:
Я нашел решение для этого, хотя оно кажется неправильным. На верхнем уровне представления я передаю UndoManager свойству, которое я храню в документе:
struct ContentView: View {
let document: MyDocument
@Environment(.undoManager) var undoManager
var body: some View {
document.setUndoManager(undoManager: undoManager)
return TopLevelView(document: document)
}
}
Ответ №3:
После попыток разобраться в этом более одного дня, мой вывод заключается в том, что UndoManager
в Environment
— это тот, который привязан к 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(amp;state, action)
undoManager.registerUndo(withTarget: self){target in
target.send(action.inverted())
}
}
//...other methods...
}
Store
конечно, это может быть ваш класс document. Теперь вы можете передавать UndoManager
найденные в среде из любого представления, которое отправляет действия (обратите внимание на таблицы и оповещения). Или вы автоматизируете этот шаг:
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)
}
}
(возможно, вам нужно было бы поместить Dispatcher
в StateObject
, я не тестировал эту часть, потому что я счастлив передать диспетчер отмены в качестве аргумента функции в моем небольшом приложении).
Комментарии:
1. Как бы это работало, если бы у вас было такое дополнение, как промежуточное программное обеспечение; выполнение какого-либо действия, которое не исходит из события кнопки / пользовательского интерфейса? Из того, что я могу сказать, .UndoManager равен нулю, если первое не является истинным (что очень … раздражает)