NSUndoManager -setActionName: being ignored on Mac Catalyst

So I just noticed that my calls to -setActionName: on NSUndoManager are not properly updating the Undo/Redo menu item titles in the menu bar on Mac Catalyst. At first I thought maybe my actionName string was too long, so I just created a really simple sample project and ...yeah...my calls to -setActionName are ignored. The menu bar items just say "Undo" and "Redo.."

Sample:

-(IBAction)changeBackgroundColorAction:(UIButton*)sender

{
    UIColor *currentBGColor = self.view.backgroundColor;
    self.view.backgroundColor = UIColor.yellowColor;

    NSUndoManager *undoManager = self.undoManager;
    [undoManager registerUndoWithTarget:self selector:@selector(setBackgroundColorWithColor:) object:currentBGColor];
    [undoManager setActionName:@"Change BG Color"];

}

I'm really starting to regret not using AppKit...

This works around the problem. I'm sharing it with the community because I'm that type of guy:

  1. Add the following methods in the responder chain:
-(void)redo:(id)sender;
-(void)undo:(id)sender;
  1. Implement the methods like so:
-(void)redo:(id)sender
{
    NSUndoManager *undoManager = self.undoManager;
    [undoManager redo];
}

-(void)undo:(id)sender
{
    NSUndoManager *undoManager = self.undoManager;
    [undoManager undo];
}

Validate the methods:

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
     if (action == @selector(undo:))
    {
          return self.undoManager.canUndo;
    }
    else if (action == @selector(redo:))
   {
       return self.undoManager.canRedo;
   }
   
    return [super canPerformAction:action withSender:sender];
}

Then the undo/redo menu items in the menu bar will update with the action name proper. You're welcome.

I filed FB11747430

So tried to workaround the issue by implementing -validateCommand: but apparently this isn't called on menu bar items provided by the system. What am I supposed to do reimplement the entire Edit menu?

Accepted Answer

This works around the problem. I'm sharing it with the community because I'm that type of guy:

  1. Add the following methods in the responder chain:
-(void)redo:(id)sender;
-(void)undo:(id)sender;
  1. Implement the methods like so:
-(void)redo:(id)sender
{
    NSUndoManager *undoManager = self.undoManager;
    [undoManager redo];
}

-(void)undo:(id)sender
{
    NSUndoManager *undoManager = self.undoManager;
    [undoManager undo];
}

Validate the methods:

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
     if (action == @selector(undo:))
    {
          return self.undoManager.canUndo;
    }
    else if (action == @selector(redo:))
   {
       return self.undoManager.canRedo;
   }
   
    return [super canPerformAction:action withSender:sender];
}

Then the undo/redo menu items in the menu bar will update with the action name proper. You're welcome.

Just to add another note...the system doesn't appear to enumerating through the responder chain in the proper order when validating undo/redo commands. For example if a UITextField has focus/firstResponder status -canPerformAction:withSender: is hitting my responder implementing this workaround first and you could accidentally swallow the UITextField's undo/redos with this workaround if you're not careful...

So you also have to check that a text field is first responder/has focus and if it does call through to super so you don't swallow the text field's undo/redo. Not sure why the system doesn't start validating undo/redo starting from the first responder, then enumerate responders backward until undo/redo validates. The system seems to be starting from the UIWindow and digging through the responder chain in the wrong order.

Note that calling through to super from -canPerformAction:withSender: when the text field has focus will leave you with an Undo/Redo menu item that doesn't have a proper action name (it should say "Undo Typing"). This can be fixed, if you are determined enough to write even more workaround code to get the proper behavior.

This is kind of a nightmare. I'm spending so much time implementing workarounds like this. Not sure if skipping AppKit with this UIKit shortcut is actually saving me anytime at all, but I'm in too deep now though.

NSUndoManager -setActionName: being ignored on Mac Catalyst
 
 
Q