Core Data Undo/Redo - Action depends on what was undone

1.7k Views Asked by At

I have a somewhat complex data model in my iPad application (an OpenGL drawing app), and I'm working on implementing undo/redo functionality. I like the fact that Core Data will undo data model changes for free, but I don't know if the built-in functionality will be sufficient for me.

I have seen lots of examples of undo/redo being implemented in drawing apps, however they generally do the following:

  1. Tell the managed object context to undo.
  2. Redraw everything on the page from the changed data model.

This is horribly inefficient - in my app I need to be able to perform an action to undo based on the particular object that is being 'undone', and often this means refreshing only part of the canvas.

So my question is this: can I register my own undo operations and use that in conjunction with the built-in undos? For example, say I do the following when the user draws a line:

- (void)drawLineFromPoint:(CGPoint)startPoint toPoint:(CGPoint)endPoint
{
    // Register the undo operation.
    [[[managedObjectContext undoManager] prepareWithInvocationTarget:self] 
                                               removeObjectWithIndex:nextObjectIndex];

    // Draw the line object.  
    [self drawLineObjectWithIndex:nextObjectIndex fromPoint:startPoint toPoint:endPoint];

    // Save the new object to the data model.
    [MyCoreDataHelper saveLineObjectWithIndex:nextObjectIndex fromPoint:startPoint toPoint:endPoint];

    nextObjectIndex++;
}

When I come to undo this action, will the invocation be fired and the model changed appropriately? Or does this situation require that I abandon the built-in managed object context undo system and roll my own using just the NSUndoManager, including deleting and editing the data model myself? Unless I can tell what the built-in undo/redo is actually undoing, it looks like this could get horribly messy and complicated...

Edit: I suspect (if the idea above was to work) I would need to wrap it into an undo group, so that my registered undo operation would be grouped with the data model changes?

Another Edit: Also, can I guarantee the order in which undo actions will be carried out? In other words, if I call undo after the core data changes are saved and my undo action is registered, can I be sure that a deleted entity will be reinstated before my undo action is called?

2

There are 2 best solutions below

1
On BEST ANSWER

Do you know then if it is possible to identify what data was changed from the undo?

Why don't you check out the notifications for NSManagedObjectContext (Apple Docs)

Specifically look at the notification's userInfo with the keys NSInsertedObjectsKey, NSUpdatedObjectsKey, and NSDeletedObjectsKey.

Also, can I guarantee the order in which undo actions will be carried out?

Well, undo works via stack (last in, first out), so you "should" be able to trace your steps. Obviously you have to be very specific in the order you register your undos if you want things to unfold correctly. A few lines of code that help me trace the undo stack just to see what is behind the veil of mystery:

id undoStack, redoStack;
object_getInstanceVariable(undoManager, "_undoStack", &undoStack);
object_getInstanceVariable(undoManager, "_redoStack", &redoStack);
NSLog(@"%@", [undoStack description]);
NSLog(@"%@", [redoStack description]);

(Props to this site for the above code)

4
On

Use the flyweight structure. It is generally used for undo/redo operations. What you're asking requires you to do this yourself.

Flyweights can store as much data as you want, and you can remove them from your undo sequence whenever you want. Your flyweights, for example, could store the pixel data before each edit was made. This would take large amounts of memory, but you could easily undo something without redrawing the entire scene.