You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在NSView/NSViewController中配置UndoManager以更新撤销菜单动作名称

macOS自定义文本视图撤销菜单配置方案

核心问题定位

你的问题根源在于UndoManager未被响应链正确识别,导致系统无法自动读取撤销动作的状态(是否可撤销)和名称。重写undo/redo方法会绕过系统的菜单状态判断逻辑,虽然功能可用但无法同步菜单文本。以下是修复步骤:


1. 调整第一响应者逻辑

将第一响应者从TextViewController切换到交互视图TextView——系统会从第一响应者开始遍历响应链查找undoManager,而文本输入的交互主体是TextView:

class TextViewController: NSViewController {
    let textController = TextController()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        guard let textView = view as? TextView else { return }
        textView.textController = textController
        
        // 延迟让TextView成为第一响应者(确保视图已加载完成)
        DispatchQueue.main.async {
            textView.becomeFirstResponder()
        }
    }
    
    // 可选:让控制器也暴露undoManager,强化响应链覆盖
    override var undoManager: UndoManager? {
        return textController.undoManager
    }
}

2. 正确重写TextView的undoManager属性

确保TextView的undoManager直接返回TextController的实例,让响应链能正确获取到管理撤销状态的对象:

class TextView: NSView {
    var textController: TextController!
    
    // 必须重写此属性,而非自定义getter
    override var undoManager: UndoManager? {
        return textController.undoManager
    }
    
    // 确保TextView允许成为第一响应者
    override var acceptsFirstResponder: Bool {
        return true
    }
    
    // 处理文本输入事件示例
    override func keyDown(with event: NSEvent) {
        guard let char = event.characters?.first else {
            super.keyDown(with: event)
            return
        }
        textController.insertCharacter(char)
        // 触发菜单状态更新
        NSApp.updateWindows()
        super.keyDown(with: event)
    }
}

3. 规范撤销动作的注册方式

在TextController中正确注册撤销动作并设置名称,确保系统能读取到动作信息:

class TextController {
    let undoManager = UndoManager()
    private var text = ""
    
    func insertCharacter(_ char: Character) {
        let oldText = text
        text.append(char)
        
        // 注册撤销逻辑
        undoManager.registerUndo(withTarget: self) { [weak self] target in
            guard let self = self else { return }
            self.text = oldText
            // 可选:如果需要同步UI,这里可以触发TextView刷新
        }
        // 设置动作名称,系统会自动拼接为"Undo Typing"/"Redo Typing"
        undoManager.setActionName("Typing")
    }
}

4. 移除自定义的undo/redo方法

删除TextView中重写的undo()和redo()方法——系统会通过undoManager的canUndo/canRedo属性自动控制菜单的激活状态,同时读取actionName更新菜单文本。


关键注意事项

  • 每次注册撤销动作后调用NSApp.updateWindows(),强制系统刷新菜单状态。
  • 确保响应链的完整性:TextView的nextResponder默认是TextViewController,控制器的nextResponder会自动连接到窗口,无需额外修改。
  • 不要手动修改菜单的标题或状态,完全交由UndoManager和系统响应链处理。

内容的提问来源于stack exchange,提问作者georgemp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 04:50:09