NSUndoManager made simple (Xcode/iOS)


Building undo and redo actions needn't be complicated. In the following example I have three buttons wired up from a view controller in the storyboard: a change colour button, an undo button and a redo button.

NSUndoManager

The change colour button does one simple thing, which is to change the background colour, but each time it does this it captures the current colour and sends this information to the NSUndoManager and tells it that if it ever needs to undo the action, then simply send the current colour (i.e. what has become the old colour) as the new colour to the same method.

In terms of programming what is happening, when the undo is called, is that the colour is set again before the undo is recursively taken off the stack. One thing to notice is that the NSUndoManager when implementing the method ignores the call to prepareWithInvocationTarget: or rather it adds the call to the redo stack.

A redo is an undo of an undo! And in contrast to an undo, a redo is a new action and registers an undo like any other call to the method (and adds an action to the undo stack).

#import "UndoActionsViewController.h"

@interface UndoActionsViewController ()

@end

@implementation UndoActionsViewController
@synthesize myObject;
NSUndoManager *undoManager;
- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
    undoManager = [[NSUndoManager alloc] init];
 
    myObject.backgroundColor=[UIColor yellowColor];
 

 
 
}

-(void)viewDidAppear {
  //  [undoManager undo];
   }

- (void)setMyObjectColor:(UIColor *)color {
 
    UIColor *currentColor = myObject.backgroundColor;
 
    if ((color != currentColor)) {
        [[undoManager prepareWithInvocationTarget:self]
         setMyObjectColor:currentColor];
        [undoManager setActionName:NSLocalizedString(@"Color Change", @"color undo")];
        myObject.backgroundColor=color;
     
    }
 
 
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)changeColor:(id)sender {
    [self setMyObjectColor:[UIColor blueColor]];

}

- (IBAction)undo:(id)sender {
     [undoManager undo];
}

- (IBAction)redo:(id)sender {
    [undoManager redo];
}
@end

Further reading

Registering Undo Operations (Apple, Developer)

Note

I read Matt Neuburg's discussion of NSUndoManager subsequent to posting this blog, and he writes almost exactly the same remark about a redo: “Redo is the Undo of an Undo”. So keep this in mind at all times.

See also Neuburg for a discussion of grouped undos and further advanced use of NSUndoManager.

Endorse on Coderwall

Comments