Core Data and Undo. Was: NSUndoManager retain/release
Core Data and Undo. Was: NSUndoManager retain/release
- Subject: Core Data and Undo. Was: NSUndoManager retain/release
- From: Jerry Krinock <email@hidden>
- Date: Tue, 11 Jan 2011 21:47:48 -0800
On 2011 Jan 11, at 17:54, Dave Fernandes wrote:
> And I'd love to know a trick to bring the correct tab to the front when undoing/redoing changes in a tabbed interface.
Well, the way I've seen it done in Apple Sample Code is to avoid a tabbed interface, do editing of objects in sheets, and the sheets have their own managed object contexts and own undo managers. But I don't like that because the undo chain often appears to be broken from the user's viewpoint, objects must be copied between mocs when editing ends, and as far as multiple undo managers, well, I say "Thank you", but one undo manager is more than enough for me to handle on a good day.
So, I've done the switching of tabs as you suggest, and am fairly pleased with the results, except that I'm sure I spent much more time on Undo than is worth it to my users.
First of all, you make the simplifying assumption that the "correct" tab view item is the one that is being displayed at the time that the Undo Group was ended. I find this to be correct about 90% of of the time. Then, you register an undo action to select this tab view item in your current undo group.
It sounds simple, but when you try it, all kinds of wacky things happen. You also need to consider Redo. The plot thickens.
I do it via a notification with two observers:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(registerTabViewForUndo:)
name:SSYUndoManagerWillEndUndoGroupNotification
object:[self undoManager]] ;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(registerTabViewForUndo:)
name:NSUndoManagerCheckpointNotification
object:[self undoManager]] ;
SSYUndoManagerWillEndUndoGroupNotification is, as the name implies, a notification that I post when I end an undo grouping, usually a super-group that includes a half dozen or so Core Data undo groupings. (But that's another topic.)
Now here's the method that it triggers. The comments are the most interesting part.
- (void)registerTabViewForUndo:(NSNotification*)note {
// As always, I use [[self managedObjectContext] hasChanges]
// instead of [self isDocumentEdited] because the former
// often returns YES when the latter is NO, and this
// seems to be the true answer.
if (![[self managedObjectContext] hasChanges]) {
return ;
}
// Some tab view items do not have any controls which
// affect the data model.
if (![[self bkmslfWinCon] activeTabViewIsUndoable]) {
return ;
}
// Determine whether or not to register a change to the current tab view in the current
// undo group. The code with follows looks weird and unsymmetrical, but it implements
// (efficiently) what has been determined by careful experimentation to be the
// following Magic Algorithm:
// doRegister if any one of:
// 1. SSYUndoManagerWillEndUndoGroupNotification && !isUndoing
// (Register during normal "do" changes by the user, to be used during later Undo.)
// 2. NSUndoManagerCheckpointNotification && isUndoing
// (Register during undo operations, to be used during later Redo.)
// 3. NSUndoManagerCheckpointNotification && isRedoing
// (Register during redo operations, to be used during later Undo.)
// I tried combining cases 1 and 2 above, by observing NSUndoManagerCheckpointNotification
// without regard to isUndoing, but this caused a proliferation of undo groups, needing to
// click Undo several times before the desired action was undone.
// I also tried to use NSUndoManagerWillCloseUndoGroupNotification instead of
// SSYUndoManagerWillEndUndoGroupNotification, but this caused a "bad group state -
// attempt to close a nested group with no group open" to be logged from GCUndoManager
// immediately upon doing an action.
NSString* name = [note name] ;
BOOL doRegister = NO ;
if ([name isEqualToString:NSUndoManagerCheckpointNotification]) {
doRegister = [[self undoManager] isUndoing] || [[self undoManager] isRedoing] ;
}
else if ([name isEqualToString:SSYUndoManagerWillEndUndoGroupNotification]) {
doRegister = ![[self undoManager] isUndoing] ;
}
if (doRegister) {
// Register revealing the tab view item in the current undo group
NSString* currentTabViewIdentifier = [[self bkmslfWinCon] activeTabViewItemIdentifier] ;
[[self undoManager] registerUndoWithTarget:self
selector:@selector(revealTabViewIdentifier:)
object:currentTabViewIdentifier] ;
}
}
Of course, bkmslfWinCon is the document's window controller, and the -activeTabViewItemIdentifier and -revealTabViewIdentifier: methods are as their names imply. I use these wrappers since I actually have multiple levels of tabs.
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden