UndoManager.setActionName not displayed in menu (SwiftUI macOS)

Viewed 222

The Edit menu's Undo/Redo displayed title does not reflect the setActionName in this demo SwiftUI macOS app. The undo/redo functionality works fine and the manager reports back that it has set the action title.

Why is the menu not updated?

autoenablesItems is true for NSApp.menu. When looping through all Windows in NSApp (just one window), the UndoManager (just one for the app) is the same instance as the one SwiftUI presents via Environment. Checking the undo item title via NSApp's reference also shows the item title is set, even though it is not displayed in the Edit menu.


struct ContentView: View {
    @Binding var document: DocumentTestDocument
    @Environment(\.undoManager) var undo
    @StateObject var vm = VM()
    
    @State var autoEnables = false
    
    var body: some View {
        VStack(spacing: 25) {
            HStack {
                Button("<") { vm.performUndo(undo: undo) }
                    .disabled(!(undo?.canUndo ?? true))
                
                Button("Up") { vm.increment(undo: undo) }
                Button("Down") { vm.decrement(undo: undo) }
                
                Button(">") { vm.performRedo(undo: undo) }
                    .disabled(!(undo?.canRedo ?? true))
            }
            
            Text(String(vm.count))
                .font(.title)
            
            Text("MenuItemTitle \(vm.title)")
            
        }
        .controlSize(.large)
        .font(.title3)
        .frame(width: 400, height:  300)
        .onAppear { DispatchQueue.main.async { autoEnables = NSApp.menu?.autoenablesItems ?? false } }
    }
}

class VM: ObservableObject {
    
    @Published var count = 0
    @Published var title = ""
    
    func increment(undo: UndoManager?) {
        count += 1
        
        undo?.registerUndo(withTarget: self, handler: { (targetSelf) in
            targetSelf.decrement(undo: undo)
        })
        undo?.setActionName("Increment")
        
        title = undo?.undoMenuItemTitle ?? "Nil"
    }
    
    func decrement(undo: UndoManager?) {
        count -= 1
        
        undo?.registerUndo(withTarget: self, handler: { (targetSelf) in
            targetSelf.increment(undo: undo)
        })
        undo?.setActionName("Increment")
        
        title = undo?.undoMenuItemTitle ?? "Nil"
    }
    
    func performUndo(undo: UndoManager?) {
        undo?.undo()
    }
    
    func performRedo(undo: UndoManager?) {
        undo?.redo()
    }
}
1 Answers

For some reason, this is not implemented in the new SwiftUI App lifecycle. If you set up your Edit menu in a storyboard and configure the NSHostingView yourself, the Undo menu item titles will change correctly with your existing code. I sure hope this feature is on the way soon, because well-named undos are my favorite part of a polished Mac app!

Related