I had a similar situation where I wanted to conditionally disable certain undo/redo operations when the app is in a specific state (while still allowing undo/redo for other operations).
The method of implementing - (BOOL)validateMenuItem:(NSMenuItem *)item
on a view doesn't work for me (I have a document-based app on 10.12). Per the docs at https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MenuList/Articles/EnablingMenuItems.html:
If there is an object in the responder chain that implements the item’s action, NSMenu then checks to see if that object implements the validateMenuItem: or validateUserInterfaceItem: method. If it does not, then the menu item is enabled. If it does, then the enabled status of the menu item is determined by the return value of the method.
The view would have to add an undo method the does the right thing as well.
When I probed the responder chain, I found that my NSWindow was the object that responded to undo:
(though it's not part of the documented interface), so my current plan is to use a custom NSWindow subclass with the imeplementation of validateMenuItem
, along the lines of:
#import "Window.h"
@implementation SBXWindow
- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag screen:(NSScreen *)screen
{
self = [super initWithContentRect:contentRect styleMask:style backing:bufferingType defer:flag screen:screen];
return self;
}
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
// Call super imeplementation as it appears to update the menu item title (and potentially other stuff)
BOOL result = [super validateMenuItem:item];
if (result == NO) {
return NO;
}
if (item.action == @selector(undo:) || item.action == @selector(redo:)) {
// Add custom logic here
}
return result;
}
@end
However there are warnings that the undo:
redo:
methods aren't implemented. These can be eliminated by creating a category on NSWindow, such as:
@interface NSWindow (SBXUndoable)
- (void)undo:(id)sender;
- (void)redo:(id)sender;
@end
Not sure if there are any issues with doing that (I didn't notice any), but it does eliminate the warnings. I've since changed the class to a Swift class, which didn't have any warnings to deal with.