The application I'm working on allows users to manage some assets. The user can create / delete / edit / split / move assets on the screen. Users should be able to undo all of these steps.
Assets are managed using master data (and yes, an undoManager is created).
For each of these actions, I create cancellation groups with this pair:
beginUndoGrouping ... endUndoGrouping Here a simple example (sequence 1): // SPLIT - (void) menuSplitPiece: (id) sender { [self.managedObjectContext.undoManager beginUndoGrouping]; [self.managedObjectContext.undoManager setActionName:@"Split"]; //... do the split [self.managedObjectContext.undoManager endUndoGrouping]; // if the user cancels the split action, call [self.managedObjectContext.undoManager undo] here; }
I do the same for editing: if the user cancels the editing, I call the cancel immediately after endUndoGrouping .
Everything works beautifully with one exception: in addition to the groups that I create, there are other groups created by Core Data that I cannot control . Here is what I mean:
I registered to receive NSUndoManagerDidCloseUndoGroupNotification notifications as follows:
- (void) registerUndoListener { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didCloseUndoGroup:) name:NSUndoManagerDidCloseUndoGroupNotification object:nil]; ...}
I use these notifications to update the Cancel button and display the name of the action that is canceled as a result of some action: for example. Cancel split
However, didCloseUndoGroup is called / notified twice for each action above (for example, section 1 after endUndoGrouping):
During the first notification, self.managedObjectContext.undoManager.undoActionName contains the specified undo name that I expected, and the second time undoActionName is the empty string.
As a workaround, I tried to just cancel operations that had an empty name (assuming they weren't mine and I didn't need them) and see if anything was missing.
Now didCloseUndoGroup looks like this
- (void) didCloseUndoGroup: (NSNotification *) notification { ... if ([self.managedObjectContext.undoManager.undoActionName isEqualToString:@""]){ [self.managedObjectContext.undoManager undo]; } [self refreshUndoButton];
And magically this works, I can undo any command, any number of layers, using undo. But this is not how it should work ...
A few other things I've tried before this:
- [self.managedObjectContext processPendingChanges] before opening any grouping. He still sent two notifications.
- Another thing I've tried is disableUndoRegistration / enableUndoRegistration. This threw an exception: "invalid state, undo is called with too many nested undo groups"
None of the above helped me "isolate" the mysterious groups that I mentioned earlier.
I should not receive NSUndoManagerDidCloseUndoGroupNotification notifications twice. Or, right? Is there a better way to handle this situation?
UPDATE This is what finally worked. I used to automatically cancel groups without a name as soon as I received a notification. This was the cause of this problem. Now I cancel everything until I reach my target group, and then make the last cancellation for this group.
"undoManagerHelper" is just a stack management system that generates a unique identifier for each command that is pushed onto the stack. I use this unique identifier to name a group.
- (BOOL) undoLastAction { NSString *lastActionID = [self.undoManagerHelper pop];