Reputation: 476
Why does the @Environment
UndoManager
not update its canUndo
property when it has actions in its stack? I have a view that has a child that can utilize the un/redo functionality, but for some reason I can't disable the undo button based on the manager.
struct MyView: View {
@Environment(\.undoManager) var undoManager: UndoManager?
var body: some View {
Button("Undo") { ... }
.disabled(!self.undoManager!.canUndo)
}
}
Upvotes: 7
Views: 1952
Reputation: 257729
UndoManager.canUndo
is not KVO compliant, so use some notification publisher to track state, like below
struct MyView: View {
@Environment(\.undoManager) var undoManager
@State private var canUndo = false
// consider also other similar notifications
private let undoObserver = NotificationCenter.default.publisher(for: .NSUndoManagerDidCloseUndoGroup)
var body: some View {
Button("Undo") { }
.disabled(!canUndo)
.onReceive(undoObserver) { _ in
self.canUndo = self.undoManager!.canUndo
}
}
}
Upvotes: 14
Reputation: 6181
When it comes to canRedo
I tried multiple things, and what I ended up with is this - so observing viewModel
(or document
or any other undo-supporting data source) and updating canUndo
/canRedo
in reaction to it's change:
struct MyView: View {
@ObservedObject var viewModel: ViewModel
@Environment(\.undoManager) private var undoManger: UndoManager!
@State private var canUndo = false
@State private var canRedo = false
var body: some View {
RootView()
.onReceive(viewModel.objectWillChange) { _ in
canUndo = undoManger.canUndo
canRedo = undoManger.canRedo
}
if canUndo {
Button(
action: { undoManger?.undo() },
label: { Text("Undo") }
)
}
if canRedo {
Button(
action: { undoManger?.redo() },
label: { Text("Redo") }
)
}
...
I also wrapped it in a standalone button (without overgeneralizing the implementation above my own needs) that eliminates the boilerplate from my view and keeps complexity more private so it ends up like this for me:
struct MyView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
RootView()
UndoManagerActionButton(
.undo,
willChangePublisher: viewModel.objectWillChange
)
UndoManagerActionButton(
.redo,
willChangePublisher: viewModel.objectWillChange
)
...
Upvotes: 1