3

Resolution

NSUndoManager must only be used in a child NSManagedObjectContext (when used with Core Data). This is because the UIManagedDocument may auto-save at any point in time, after which an undo will have no effect. Therefore there is no point using NSUndoManager to just achieve save/cancel functionality, since a child context will give you the same result.

Bit sad really, because NSUndoManager is a lot easier to implement than a child context (for the latter I have to call existingObjectWithID to copy objects from the parent to the child - painful). Personally I would have thought the document should not auto-save if groupingLevel != 0. Rant finished.

Original Question

I have a table view controller that loads data using Core Data into a UIManagedDocument. It segues to a view controller to edit each row in the table. In that view controller I have cancel and save buttons. I am implementing the cancel capability using NSUndoManager through a category on my NSManaged object (self.list below).

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.list beginEdit];
}

- (IBAction)cancel:(id)sender
{
    [self.list cancelEdit];
    [self close];
}

- (IBAction)save:(id)sender
{
    [self.list endEdit];
    [self close];
}

The category implements beginEdit, endEdit and cancelEdit which is intended to handle the NSUndoManager stuff. In the code below, useUndo is a constant that I set to NO or YES to see the impact of using NSUndoManager.

- (void)beginEdit
{
    if (useUndo)
    {
        NSUndoManager *undoManager = [[NSUndoManager alloc] init];
        self.managedObjectContext.undoManager = undoManager;
        [undoManager beginUndoGrouping];
    }
}

- (void)endEdit
{
    [self.managedObjectContext save:nil];
    if (useUndo)
    {
        NSUndoManager *undoManager = self.managedObjectContext.undoManager;
        [undoManager endUndoGrouping];
        self.managedObjectContext.undoManager = nil;
    }
}

- (void)cancelEdit
{
    if (useUndo)
    {
        NSUndoManager *undoManager = self.managedObjectContext.undoManager;
        [undoManager endUndoGrouping];
        [undoManager undo];
    }
}

I can see the Core Data debug messages showing it is committing the changes if I save an object and click the Home button when useUndo = NO. However, with useUndo = YES, it does not auto-save when I click on the Home button. I have waited a couple of minutes, and it still doesn't autosave. Is there some way I can force an auto-save?

Can anybody explain why using undoManager causes this change in behaviour?

I suspect either I am going about this the wrong way, or have some simple problem in the code. Any help would be appreciated.

Michael
  • 8,891
  • 3
  • 29
  • 42
  • First of all, never call `[self.managedObjectContext save:nil]` (if it's not a childContext) when using an `UIManagedDocument`. You should always call `updateChangeCount:` on your UIManagedDocument object. See: [Using a Managed Document’s Managed Object Context](http://developer.apple.com/library/ios/#documentation/uikit/reference/UIManagedDocument_Class/Reference/Reference.html) – Florian Mielke Apr 19 '13 at 09:42

1 Answers1

1

I'm not sure if it's correct but other answers on stackoverflow have mentioned that an NSUndoManager clears the undo stack when the context saves. That means that using an undo manager with auto-save would at most be useful for a couple of seconds (whatever the auto-save interval is). There might be a connection there, I'm trying to find out more...

Andreas
  • 2,665
  • 2
  • 29
  • 38
  • Interesting - it matches other behavior I am seeing where I begin an undo group, make some changes, and then do an undo. Sometimes auto-save occurs between the begin and undo, and so undo undoes nothing. This is less than useless! Any concrete evidence this behavior is by-design would be helpful. I couldn't find other answers on NSUndoManager clearing the undo stack, or anything in the Apple doco. – Michael Aug 28 '13 at 02:24
  • I have some more info. The managed document only auto-saves its own two contexts, so you'll have to create your own sub-context to prevent auto-saves at bad times. Then save the context manually. That said, I don't think the undo manager is supposed to manage an undo stack for your users, only for your core data operations. – Andreas Aug 30 '13 at 12:37
  • To me the upshot is that if you are going to use NSUndoManager, it has to be in a child context. Therefore, if the requirement is just to support save/cancel as opposed to true undo/redo, there is no value in using NSUndoManager as it is accomplished by either saving or discarding the child context. I will mark your answer as correct. – Michael Sep 04 '13 at 01:50