Undoable Cocoa Bindings

I was reading Scott Stevenson's wonderful tutorial CocoaDevCentral, Intro To Bindings

In it, he uses an NSMutableDictionary as sort of a generic struct, so the model of his program is an array of objects, each holding a sub-array of other objects. All well and good, a 3-pane app master-detail view, rather like iTunes, Mail, and NetNewsWire.

Fine, until I tried to add undo.

Patrick Machielse gave me the key insight I needed, and Scott Stevenson also helped.

This version of the project: MailDemo - Undoable Cocoa Bindings.zip uses a generic class, UndoableProperties, that implements an "properties" NSMutableDictionary and gives you undo/redo, and sets the undo menu item.

See the revised Mailbox.m and Email.m to see how you use it.

The entire Email implementation is given here:

@implementation Email

- (id) init {
  if (self = [super init]) {
    static int counter = 0;
    NSArray * keys = [NSArray arrayWithObjects: @"address", @"subject", @"date", @"body", nil];
    NSArray * values    = [NSArray arrayWithObjects: @"test@test.com", 
      [NSString stringWithFormat:@"Subject %d", ++counter], [NSDate date], [NSString string], nil];
    [self observeProperties:[NSDictionary dictionaryWithObjects: values forKeys: keys]];
  }
  return self;
}

- (NSUndoManager *)undoManager {
  return [ (MyController *) [NSApp delegate] undoManager];
}

In this version of Mail Box Demo, you can create and destroy mailboxes and emails, change mailbox names, change email fields, and all these operations are undoable with the multiple undo one expects in a Cocoa program.

Under the hood, UndoableProperties is handling

[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionOld context:NULL]
for all the keys in the dictionary passed to it.

I've set the "enable" bindings on the "+" and "-" buttons so they are only available when they would work. Scott Stevenson gave me some pointers on this one.

I've subclassed [NSArrayController remove] so it sets the undo menu item appropriately, in a fairly generic way. (I'm not happy with how it finds the appropriate undoManager to use. It works for this program, but wouldn't work in general.)

I have the remaining open questions:

Can I cascade this, creating an UndoableProperties that holds as one of its key,vlaue pairs a further UndoableProperties?

Suppose the value is a NSMutableArray. Can UndoableProperties be extened to automatically make the array KVC compliant and undoable?

by David Phillip Oster. Page last modified March 30, 2007