Cocoa非NSDocument应用Undo/Redo菜单项与NSUndoManager关联异常
针对你遇到的Undo/Redo菜单项始终启用、动作名称不更新的问题,以下是几个可能的错误点及解决思路:
First Responder链的UndoManager传递异常
NSWindow会优先询问当前first responder的undoManager方法,只有当返回nil时才会调用windowWillReturnUndoManager:。如果你的核心操作类(如数据控制器、自定义视图)没有重写-undoManager返回你维护的实例,窗口可能拿到的不是你实际在用的UndoManager,导致菜单项和业务逻辑的Undo栈断联。
解决:在负责操作的核心类中重写-undoManager,返回你统一维护的NSUndoManager对象,确保first responder链能传递正确的实例。Undo动作注册与动作名称设置时机错误
setActionName:@"Move To Trash"必须在注册Undo动作的同一个RunLoop周期内,且在动作注册之后调用,否则无法更新菜单项显示。正确的写法示例:// 先注册撤销动作 [[self.undoManager prepareWithInvocationTarget:self] undoMoveToTrash:targetItem]; // 再设置动作名称 [self.undoManager setActionName:@"Move To Trash"];另外,如果注册Undo动作时没有使用标准的
registerUndoWithTarget:...或block形式的API,UndoManager无法正确追踪栈状态,也会导致菜单项状态不更新。菜单项启用状态被手动固定
如果Undo/Redo菜单项在XIB/Storyboard中勾选了「Enabled」,或者代码中手动设置了menuItem.enabled = YES,会覆盖NSUndoManager自动管理的启用状态,导致菜单项始终处于启用状态。
解决:打开XIB/Storyboard,找到这两个菜单项,取消「Enabled」的勾选,让UndoManager自动控制其启用/禁用。UndoManager栈状态未正确更新
执行操作、撤销、重做后,打印undoManager.canUndo、undoManager.canRedo和undoManager.undoActionName的值,验证这些状态是否符合预期。如果这些值本身就异常(比如canUndo始终为YES),说明你可能重复注册了动作,或者在执行新操作时没有正确清理栈(比如需要时调用removeAllActions)。控件自带UndoManager干扰
部分Cocoa控件(如NSTableView、NSOutlineView)自带UndoManager,如果这些控件成为first responder时返回了自己的UndoManager,会导致窗口使用的实例和你维护的不一致。
解决:检查视图层级中的这类控件,在其子类或代理中重写-undoManager,返回你统一维护的UndoManager实例,或者返回nil让窗口使用你的实例。
内容的提问来源于stack exchange,提问作者silverdr

