cocoaundonsundomanager

How can I make NSUndoManager's undo/redo action names work properly?


I'm learning Cocoa, and I've gotten undo to work without much trouble. But the setActionName: method is puzzling me. Here's a simple example: a toy app whose windows contain a single text label and two buttons. Press the On button and the label reads 'On'. Press the Off button and the label changes to read 'Off'. Here are the two relevant methods (the only code I wrote for the app):

-(IBAction) turnOnLabel:(id)sender
{
    [[self undoManager] registerUndoWithTarget:self selector:@selector(turnOffLabel:) object:self];
    [[self undoManager] setActionName:@"Turn On Label"];
    [theLabel setStringValue:@"On"];
}

-(IBAction) turnOffLabel:(id)sender
{
    [[self undoManager] registerUndoWithTarget:self selector:@selector(turnOnLabel:) object:self];
    [[self undoManager] setActionName:@"Turn Off Label"];
    [theLabel setStringValue:@"Off"];
}

Here's what I expect:

In fact, all these things work as I expect apart from the last one. The item in the Edit menu reads 'Redo Turn Off Label', not 'Redo Turn On Label'. (When I click that menu item, the label does turn to On, as I'd expect, but this makes the menu item's name even more of a mystery.)

What am i misunderstanding, and how can I get these menu items to display the way I want them to?


Solution

  • Remember: when you are redoing, your code must set an actionName for the Undo menu item.

    When you are undoing or redoing, the actionName in the Redo menu item is set automatically.

    setActionName: changes the Undo menu item only. The Redo menu item actionName is automated.

    When you initially setActionName: when ![[self undoManager] isUndoing], this actionName goes to the Undo menu item. When then you choose to Undo ([[self undoManager] isUndoing] == YES, no actionNames are set by you) the undoManager automatically sets this actionName to the Redo menu item and the previous undo actionName to the Undo menu item. When you then choose to Redo, you still have to pass an actionName to go to the Undo menu item.

    In other words: you have to set actionNames only when your code is not Undoing (but you must set when initially invoked or is redoing).