Reputation: 4254
I have a problem with NSUndoManager not undoing when working with a complex model.
This is my model.
I have a Singleton that takes care of the core data stuff, this is its initialization:
model =[NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSString *path = pathInDocumentDirectory(@"store.data");
NSURL *storeURL = [NSURL fileURLWithPath:path];
NSError *error = nil;
if(![psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error]) {
[NSException raise:@"Open failed" format:@"Reason: %@", [error localizedDescription]];
}
context = [[NSManagedObjectContext alloc] init];
NSUndoManager *contextUndoManager = [[NSUndoManager alloc] init];
[contextUndoManager setLevelsOfUndo:20];
[context setUndoManager:contextUndoManager];
[context setPersistentStoreCoordinator:psc];
For every entity I have an init method that calls [self initWithEntity...] and then inits some properties. This is an example of the HalfEdge entity:
- (id) initWithVertex:(Vertex*) vert inManagedObjectContext: context {
NSEntityDescription* tEntityDescription = [NSEntityDescription entityForName: @"HalfEdge"
inManagedObjectContext: context];
self = [self initWithEntity: tEntityDescription insertIntoManagedObjectContext: context];
if(self) {
self.lastVertex = vert;
[self.lastVertex addHalfEdgeObject:self];
}
return self;
}
When the user adds a new drawing I create a new Drawing entity and then let the user add points taping the screen. For every point a rutine will get executed that may add and/or remove Triangle entities, halfedges and vertex. This is the call:
[[[DrawingsStore sharedStore].managedObjectContext undoManager] beginUndoGrouping];
[delaunay addPoint:CGPointMake(localizacion.x-dummy.bounds.size.width/2, localizacion.y-dummy.bounds.size.height/2)];
[[DrawingsStore sharedStore].managedObjectContext processPendingChanges];
[[[DrawingsStore sharedStore].managedObjectContext undoManager] endUndoGrouping];
As you can see, I set an undo group for everything that happens inside that rutine.
Then when a button is pressed I'm calling [[context undoManager] undo]; but it does nothing.
I print a fetch before and after the undo and it's the same. I can see the rutine is working properly, adding all the correct entities to core data, but then it wont undo anything at all.
EDIT with sugestions from Aderstedt
Ok, I deleted the custom init method for NSManagedObject subclasses and created a class method like this:
+ (HalfEdge*) addWithVertex:(Vertex*) vert inManagedObjectContext: context {
HalfEdge* halfEdge = [NSEntityDescription insertNewObjectForEntityForName:@"HalfEdge" inManagedObjectContext:context];
if(halfEdge) {
halfEdge.lastVertex = vert;
[halfEdge.lastVertex addHalfEdgeObject:self];
}
return halfEdge;
}
And still the same result. Objects get created, undo doesn't work. (canUndo returns 1)
EDIT
Wow, I just registered for NSUndoManagerCheckpointNotification of undoManager and once I click undo it gets posted forever like in a loop. Ok, now I know I must be doing something wrong somewhere, but... where?
Upvotes: 1
Views: 1387
Reputation: 4254
Ok I found out. Turns out I was looking in the wrong place.
Trying to Debug NSUndoManager I registered for notifications and found out that NSUndoManagerCheckpointNotification was getting called over and over again.
[delaunay addPoint...] makes all the changes to the model. But at the same time there is a render routine running that renders the triangles to the screen. In that routine I set the color of those triangles. I need to do it there because I don't know the color I should put before I render the background of the screen.
Those changes to the color attribute of the NSManagedObject subclass Triangle were causing the NSUndoManagerCheckpointNotification to be fired and the undo to not work. If I remove that, undo works.
So I figured I just have to add this, so the changes made during render don't make it to the undo stack.
[[[DibujosStore sharedStore] managedObjectContext] processPendingChanges];
[[[[DibujosStore sharedStore] managedObjectContext] undoManager] disableUndoRegistration];
[renderer render];
[[[DibujosStore sharedStore] managedObjectContext] processPendingChanges];
[[[[DibujosStore sharedStore] managedObjectContext] undoManager] enableUndoRegistration];
Upvotes: 3
Reputation: 6518
You're creating NSManagedObject
instances The Wrong Way™. Use
- [NSEntityDescription insertNewObjectForEntityForName:... inManagedObjectContext...]
to insert new objects. If you want to do custom processing for the object when it is inserted, override
- (void)awakeFromInsert
in your NSManagedObject
subclass. Please check the Core Data documentation, it explicitly states that you are discouraged from overriding initWithEntity...
. Now, as for your undo problem, your call to
[delaunay addPoint:CGPointMake(localizacion.x-dummy.bounds.size.width/2, localizacion.y-dummy.bounds.size.height/2)];
... does that actually change any attributes on Core Data objects? Other instance variables, cached arrays et.c. will not be automatically registered for undo. If you do change attributes on Core Data objects, please check to see that [context undoManager]
isn't nil.
Upvotes: -1