Online Book Reader

Home Category

Cocoa Programming for Mac OS X - Aaron Hillegass [52]

By Root 853 0
identify observation messages intended for itself versus those intended for its superclass. If the message is not intended for that specific class, Maple should pass the message on to its superclass.

The solution to this problem is to use a class-specific pointer value as the context argument. We will use this approach in the following steps of this exercise as a demonstration, although it is not necessary within the conditions of this specific exercise.

Undo for Edits


The first step is to register your document object to observe changes to its Person objects. Add the following static variable and methods near the top of RMDocument.m:

// RMDocumentKVOContext enables this class to differentiate

// between its KVO messages and those intended for a superclass.

static void *RMDocumentKVOContext;

- (void)startObservingPerson:(Person *)person

{

[person addObserver:self

forKeyPath:@"personName"

options:NSKeyValueObservingOptionOld

context:&RMDocumentKVOContext];

[person addObserver:self

forKeyPath:@"expectedRaise"

options:NSKeyValueObservingOptionOld

context:&RMDocumentKVOContext];

}

- (void)stopObservingPerson:(Person *)person

{

[person removeObserver:self

forKeyPath:@"personName"

context:&RMDocumentKVOContext];

[person removeObserver:self

forKeyPath:@"expectedRaise"

context:&RMDocumentKVOContext];

}

Call these methods every time a Person enters or leaves the document:

- (void)insertObject:(Person *)p inEmployeesAtIndex:(NSUInteger)index

{

// Add the inverse of this operation to the undo stack

NSUndoManager *undo = [self undoManager];

[[undo prepareWithInvocationTarget:self]

removeObjectFromEmployeesAtIndex:index];

if (![undo isUndoing]) {

[undo setActionName:@"Add Person"];

}

// Add the Person to the array

[self startObservingPerson:p];

[employees insertObject:p atIndex:index];

}

- (void)removeObjectFromEmployeesAtIndex:(NSUInteger)index

{

Person *p = [employees objectAtIndex:index];

// Add the inverse of this operation to the undo stack

NSUndoManager *undo = [self undoManager];

[[undo prepareWithInvocationTarget:self] insertObject:p

inEmployeesAtIndex:index];

if (![undo isUndoing]) {

[undo setActionName:@"Remove Person"];

}

[self stopObservingPerson:p];

[employees removeObjectAtIndex:index];

}

- (void)setEmployees:(NSMutableArray *)a

{

for (Person *person in employees) {

[self stopObservingPerson:person];

}

employees = a;

for (Person *person in employees) {

[self startObservingPerson:person];

}

}

Now, implement the method that does edits and is its own inverse:

- (void)changeKeyPath:(NSString *)keyPath

ofObject:(id)obj

toValue:(id)newValue

{

// setValue:forKeyPath: will cause the key-value observing method

// to be called, which takes care of the undo stuff

[obj setValue:newValue forKeyPath:keyPath];

}

Implement the method that will be called whenever a Person object is edited by either the user or the changeKeyPath:ofObject:toValue: method. Note that this method puts a call to changeKeyPath:ofObject:toValue: on the undo stack with the old value for the changed key:

- (void)observeValueForKeyPath:(NSString *)keyPath

ofObject:(id)object

change:(NSDictionary *)change

context:(void *)context

{

if (context != &RMDocumentKVOContext)

{

// If the context does not match, this message

// must be intended for our superclass.

[super observeValueForKeyPath:keyPath

ofObject:object

change:change

context:context];

return;

}

NSUndoManager *undo = [self undoManager];

id oldValue = [change objectForKey:NSKeyValueChangeOldKey];

// NSNull objects are used to represent nil in a dictionary

if (oldValue == [NSNull null]) {

oldValue = nil;

}

NSLog(@"oldValue = %@", oldValue);

[[undo prepareWithInvocationTarget:self] changeKeyPath:keyPath

ofObject:object

toValue:oldValue];

[undo setActionName:@"Edit"];

}

That should do it. Once you build and run your application, undo and redo should work flawlessly.

In testing your application, you may encounter an error: The document could not be autosaved. Now

Return Main Page Previous Page Next Page

®Online Book Reader