NSManagedObjectContext doesn't refresh correctly - iphone

hi :) I have a similarly issue like in Working with the same NSManagedObjectContext in multiple tabs
background:
My managedObjectContext (further MOC) is initialised in my appDelegate class and passed throught to multiple tabs by
myViewController.managedObjectContext = self.managedObjectContext; or in the init method with self.managedObjectContext = pContext;
the flow is: the first view is a simple list of collections. The collections are fetched with a NSFetchedResultsController (myViewController : UITableViewController<NSFetchedResultsControllerDelegate>). By selecting one, you navigate deeper, but still passing this MOC.
In the next controller (detailsViewController) I list up some items of this collection what I can interact with (set switches for instance).
I also have an editingObjectContext:
// DetailsViewController.m
NSManagedObjectContext* editingContext = [[NSManagedObjectContext alloc] init];
[editingContext setPersistentStoreCoordinator:[managedObjectContext persistentStoreCoordinator]];
self.editingObjectContext = editingContext;
Now my issue: because my view has to rotate, I am using the folowing trick:
// DetailsViewController.m
DetailsView *localAct = [[DetailsView alloc] initWithManagedObjectContext:managedObjectContext ... ]
DetailsView *localSen = [[DetailsView alloc] initWithManagedObjectContext:managedObjectContext ... ]
UITableView *localContainerView = [[UITableView alloc] init];
self.containerView = localContainerView;
[localContainerView release];
//[...]
[containerView addSubview:actuatorView];
self.tableView = containerView;
further I have a button to manage this items (which of them shall be shown and which not). This button just reloads the table with a new fetchResult.
// DetailsView.m
- (void) manageItems{
managing = !managing;
[viewController setIsManaging:managing]; // parent
self.fetchedResultsController = nil;
NSError *error = nil;
[[self fetchedResultsController] performFetch:&error];
[self reloadData];
[self updateBarButton];
}
The method for putting the items into the context looks so:
// DetailsViewController.m
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// init + create predicate
NSSet* set = [sen filteredSetUsingPredicate:predicate];
if( [set count] > 0 )
{
for( Act* act in set )
{
[editingObjectContext deleteObject:act];
}
}
else
{
Act* act = [NSEntityDescription insertNewObjectForEntityForName:#"Act" inManagedObjectContext:editingObjectContext];
// do things
}
NSError *error = nil;
[[detailView fetchedResultsController] performFetch:&error];
[self.containerView reloadData];
[detailView reloadData];
}
but after I selected the items in the managed view and clicked save (manageItems), the view doesn't show them :/ i have to switch the tab or to navigate in an other controller (parent or deeper) to actualize it.
my ViewWillAppear method:
// DetailsViewController.m
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
DetailsView *detailView = se ? senView : actView;
// [do uninteresting stuff]
[detailView.fetchedResultsController performFetch:nil];
[self.tableView reloadData];
// [do uninteresting stuff]
}
and viewWillDisapper calls
- (void)saveChanges
{
if( ![editingObjectContext hasChanges] )
return;
// send save-command to server
}
In an earliert Verison where there was only 1 view it worked and I haven't changed realy much... :/ so I don't understand why the MOC is acting like it does. The "manageItems" part is nearly equal, its just a level deeper in the new version (in the DetailsView instead of the controller) ...
if someone can tell me what I can try (always saving to server when switch between managing and normal isn't a solution because the delay in the response from the server is to high for the refresh, so I have the less to flip the view. Also refreshing the views with self.tableView / detailView / self.containerView refresh brings the same result :/ ).
and a second issue: I can't call the "editingObjectContext save:" method after sending to server, because it's throwing errors and don't save at all to local database.
Error in handleChangeResponse:
Error Domain=NSCocoaErrorDomain Code=133020 "The operation couldn’t be completed. (Cocoa error 133020.)" UserInfo=0x4d8bb90 {conflictList=(
"NSMergeConflict (0x5a2fac0) for NSManagedObject (0x5a46a80) with objectID '0x5a46420 ' with oldVersion = 7 and newVersion = 8 and old object snapshot = {\n iconName = noicon;\n [...] ;\n} and new cached row = {\n iconName = noicon;\n [...] \n}"
)}
if you have questions or need some more code (i.e. of the older version) then just ask ;)
thanks in anticipation :)

It seems like I have the solution! Since IOS 5.0 there is a new method for NSManagedObjectContext :
[managedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
Found on http://pauloliveira.net/tech/core-data-merging-conflicts
Setting this attribute to the top-level MOC (in my case in the appDelegate) and no-where else! clears my merging problems ;)

I found the reason why it doesn't worked... forget everything what I wrote above... the problem was in the fetchrequest - concretely: in the predicate... in the earlier versions I used
[NSComparisonPredicate predicateWithLeftExpression: ...]
in the actualy version I use
NSString * predicateFormat = [NSString stringWithFormat: ...];
NSPredicate* predicate = [NSPredicate predicateWithFormat:predicateFormat];
because I had to extend the number of options and also edited the request itself because it made problems in the predicate (comparing a complete object (of the MOC class, extracted from the database) with an entity didn't worked, so I managed the workaround in the DetailsViewController and haven't rolled back my updates in this place :/).
Never thought to waste so much time on this problem >.< but okay, as long as it's resolved :D
I will check if the second issue (with the saving problem) still exists. If not, I will update my post, otherwise this topic isn't closed :/

This may be due to manageobject context in use of object where u'r getting this. Remove all NSManagebobject at the time when you either log out or move back. say end using app. Seems like this...
[NSManagebobjectcontext setManagedObjectsDictionary:[NSMutableDictionary dictionary]];

Related

Core Data returns Null when i save

here is my code,
hand is the entity, and addRounds is a textField
-(IBAction)save{
[self dismissViewControllerAnimated:YES completion:nil];
[self.hand setValue:self.addRounds.text forKey:#"rounds"];
NSError *error;
if (![self.managedObjectContext save:&error]) {
//Handle Error
}
NSLog(#"%#", self.addRounds.text);
NSLog(#"%#", self.hand.rounds);
}
the console out puts
2012-11-25 16:51:18.847 App[3187:c07] 1
2012-11-25 16:51:18.848 App[3187:c07] (null)
so, for some reason, its not saving properly. Could anyone please help me! Thank You!
-(IBAction)save{
if (self.managedObjectContext == nil)
{
self.managedObjectContext = [(RootAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
Hand *hand = [NSEntityDescription insertNewObjectForEntityForName:#"Hand" inManagedObjectContext:self.managedObjectContext];
[self dismissViewControllerAnimated:YES completion:nil];
hand.rounds = nil;
self.managedObjectContext = self.app.managedObjectContext;
NSError *error;
if (![self.managedObjectContext save:&error]) {
//Handle Error
}
}
Edit
So Basically There is a total of 4 Views.
1) view with Table view, user can press '+' button
2) This view allows the user to add a cell to the table view
3) allows the user to edit the table view cells
4) this is a completely different view that also allows the user to edit the table view cells.
I'm using the code at the top of the question to save in both views 3 & 4. It works perfectly in view 3 but not 4!!
UPDATE!!!
So, I recoded the app so that views 1 & 4 are the only two views in the app. When i push view 2 and view 3 in between view 1 and 4 it sets my managedObjectContext's rounds attribute to null.
Is the rounds attribute of your hand entity a string in your model?
First, you should set the type on your hand entity's rounds attribute to a number type. 16bit integer would probably suffice (we're talking about a card game, right?), but you could make it bigger if you like.
Next change your code to:
-(IBAction)save{
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
NSNumber *rounds = [numberFormatter numberFromString:numberself.addRounds.text];
[self dismissViewControllerAnimated:YES completion:nil];
[self.hand setValue:rounds forKey:#"rounds"];
NSError *error;
if (![self.managedObjectContext save:&error]) {
//Handle Error
}
NSLog(#"%#", self.addRounds.text);
NSLog(#"%#", self.hand.rounds);
Also, I'm assuming you've inserted the instance of hand that you're setting properties on somewhere before this point. Maybe I shouldn't assume. Have you already initialized this managed object instance, self.hand, to point to data in the store?
For example, I like to use lazy instantiation:
- (Hand*)hand
{
if (_hand = nil) {
_hand = [NSEntityDescription insertNewObjectForEntityForName:#"Hand" inManagedObjectContext:self.managedObjectContext];
}
return _hand;
}
This assumes a purely -create oriented- design. In most cases, you'd want to edit an existing object and update its rounds count. For this, you should attempt to retrieve a Hand that you're editing first. You'd do that with an NSFetchRequest, and there a zillions of examples of that, so I won't repeat them here. If There were no matches, this getter would create one as a fall-back. Also, best practice is to create a category for Hand (maybe Hand+Edit.m) which contains methods for retrieving different Hands, creating them, and updating common properties.
I'd create worker methods inside the Hand object category like these:
+ (void)incrementRoundsOnHand:(Hand *)hand withManagedObjectContext:(NSManagedObjectContext *)context
+ (void)incrementRoundsBy:(NSUInteger)count onHand:(Hand *)hand withManagedObjectContext:(NSManagedObjectContext *)context
// or some sort of unique identifier, date, number, etc
+ (Hand *)handWithName:(NSString *)name withManagedObjectContext:(NSManagedObjectContext *)context
I'd then have handWithName: (or whatever) do an NSFetchRequest, and if nothing matches, create a new hand and return it. Either way, you get a hand back. Important is that you don't deal with manipulating the specifics of your Hand entity outside of the Hand managed object class. Note, since these are class methods, they can be called directly.

Error when using NSMutableSet

I get the error
* Terminating app due to uncaught exception 'NSGenericException', reason: '* Collection <__NSCFSet: 0x6b66390> was mutated while being enumerated.'
when adding an new delegate to my class. Or at least, that's where I think the problem is.
This is my code: MyAppAPI.m
[...]
static NSMutableSet *_delegates = nil;
#implementation MyAppAPI
+ (void)initialize
{
if (self == [MyAppAPI class]) {
_delegates = [[NSMutableSet alloc] init];
}
}
+ (void)addDelegate:(id)delegate
{
[_delegates addObject:delegate];
}
+ (void)removeDelegate:(id)delegate
{
[_delegates removeObject:delegate];
}
[...]
#end
MyAppAPI is a singleton which I can use throughout my application. Wherever I can (or should be able to) do: [MyAppAPI addDelegate:self].
This works great, but only in the first view. This view has a UIScrollView with PageViewController which loads new views within itself. These new views register to MyAppAPI to listen to messages until they are unloaded (which in that case they do a removeDelegate).
However, it seems to me that it dies directly after I did a addDelegate on the second view in the UIScrollView.
How could I improve the code so that this doesn't happen?
Update
I'd like to clarify me a bit further.
What happens is that view controller "StartPage" has an UIScrollView with a page controller. It loads several other views (1 ahead of the current visible screen).
Each view is an instans PageViewController, which registers itself using the addDelegate function shown above to the global singleton called MyAppAPI.
However, as I understand this viewcontroller 1 is still reading from the delegate when viewcontroller 2 registers itself, hence the error shows above.
I hope I made the scenario clear. I have tried a few things but nothing helps.
I need to register to the delegate using addDelegate even while reading from the delegates. How do I do that?
Update 2
This is one of the reponder methods:
+ (void)didRecieveFeaturedItems:(NSArray*)items
{
for (id delegate in _delegates)
{
if ([delegate respondsToSelector:#selector(didRecieveFeaturedItems:)])
[delegate didRecieveFeaturedItems:items];
}
}
Scott Hunter is right. This error is thrown when you try to edit a list while iterating.
So here is an example of what you may be doing.
+ (void)iteratingToRemove:(NSArray*)items {
for (id delegate in _delegates) {
if(delegate.removeMePlease) {
[MyAppAPI removeDelegate:delegate]; //error you are editing an NSSet while enumerating
}
}
}
And here is how you should handle this correctly:
+ (void)iteratingToRemove:(NSArray*)items
{
NSMutableArray *delegatesToRemove = [[NSMutableArray alloc] init];
for (id delegate in _delegates) {
if(delegate.removeMePlease) {
[delegatesToRemove addObject:delegate];
}
}
for(id delegate in delegatesToRemove) {
[MyAppAPI removeDelegate:delegate]; //This works better
}
[delegatesToRemove release];
}
The error suggests that, while some code somewhere is in the middle of going through your list, you are modifying the list (which explains the crash after addDelegate is called). If the code doing the enumerating is the one modifying the list, then you just have to put off the modifications until the enumeration is done (say, by collecting them up in a different list). Without knowing anything about the code doing the enumerating, can't say much more than that.
A simple solution, don't use a mutable set. They are dangerous for a variety of reasons, including this one.
You can use -copy and -mutableCopy to convert between mutable and non-mutable versions of NSSet (and many other classes). Beware all copy methods return a new object with a retain count of 1 (just like alloc), so you need to release them.
Aside from having less potential for bugs, non-mutable objects are faster to work with and use less memory.
[...]
static NSSet *_delegates = nil;
#implementation MyAppAPI
+ (void)initialize
{
if (self == [MyAppAPI class]) {
_delegates = [[NSSet alloc] init];
}
}
+ (void)addDelegate:(id)delegate
{
NSMutableSet *delegatesMutable = [_delegates mutableCopy];
[delegatesMutable addObject:delegate];
[_delegates autorelease];
_delegates = [delegatesMutable copy];
[delegatesMutable release];
}
+ (void)removeDelegate:(id)delegate
{
NSMutableSet *delegatesMutable = [_delegates mutableCopy];
[delegatesMutable removeObject:delegate];
[_delegates autorelease];
_delegates = [delegatesMutable copy];
[delegatesMutable release];
}
[...]
#end
Scott Hunter is right - it's a problem with modifying the NSSet while you're enumerating over the set's items. You should have a stack trace from where the application crashes. It probably has a line where you're adding to/remove from the _delegates set. This is where you need to make the modification. It's easy to do. Instead of adding to/deleting from the set, do the following:
NSMutableSet *tempSet = [_delegates copy];
for (id delegate in _delegates)
{
//add or remove from tempSet instead
}
[_delegates release], _delegates = tempSet;
Additionally, NSMutableSet is not thread safe, so you should call your methods always from the main thread. If you haven't explicitly added any extra threads, you have nothing to worry about.
A thing to always remember about the Objective-C "fast enumeration".
There is 2 big difference between "fast enumeration" and a for loop.
"fast enumeration" is quicker than a for loop.
BUT
You can't modify the collection your enumerating over.
You can ask your NSSet for - (NSArray *)allObjects and enumerate over that array while modifying your NSSet.
You get this error when a thread tries to modify (add,delete) the array while other thread is iterating over it.
One way to solve this using NSLock or synchronizing the methods. That ways add, remove and iterate methods cannot be called in parallel.
But this will have effect on performance and/or responsiveness because any add/delete will have to wait for the thread that was iterating over the array.
A better solution inspired from Java's CopyOnWriteArrayList would be to create a copy of the array and iterate over the copy. So the only change in your code will be:-
//better solution
+ (void)didRecieveFeaturedItems:(NSArray*)items
{
NSArray *copyOfDelegates = [_delegates copy]
for (id delegate in copyOfDelegates)
{
if ([delegate respondsToSelector:#selector(didRecieveFeaturedItems:)])
[delegate didRecieveFeaturedItems:items];
}
}
Solution using locks with performance impact
//not a good solution
+ (void)addDelegate:(id)delegate
{
#synchronized(self){
[_delegates addObject:delegate];
}
}
+ (void)removeDelegate:(id)delegate
{
#synchronized(self){
[_delegates removeObject:delegate];
}
}
+ (void)didRecieveFeaturedItems:(NSArray*)items
{
#synchronized(self){
for (id delegate in _delegates)
{
if ([delegate respondsToSelector:#selector(didRecieveFeaturedItems:)])
[delegate didRecieveFeaturedItems:items];
}
}
}

Core Data Persistent Store Coordinator NSURL error

I've been working on an app that takes user ratings about mood. I am using Core Data to store this data. Initially, I am trying to store ratings and strings of "accomplishments". I've set up an entity in Core Data called "Day" with attributes date (of type Date), dailyRating (of type Int16), dailyAccomp (of type String), and dailyAccompRating (of type Int16). My app crashes within my app delegate's Core Data persistentStoreCoordinator method at the following stmnt:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"SelfEsteemBldr.sqlite"];
The error that I'm given is
[NSPathStore2 URLByAppendingPathComponent:]: unrecognized selector sent to instance 0x4d6f440.
Maybe a little background about how the error comes about might be helpful.
My Main window has a tab bar controller as the rootViewController. Within the tab for the CD model (LogViewController), I've set up a tableView Controller within a navigation controller. The nav bar has an add button, that pushes a new view that basically has textfields so the user can enter the relevant data. Within that view, there is a nav bar with a Done button. When the user is done, the Done button changes to a Save button. When I tap the Save button, the app crashes. The Save button is a UIButtonItem within ViewDidLoad. Here's the code for the save button:
UIBarButtonItem *newSaveButton =
[[UIBarButtonItem alloc]
initWithTitle:NSLocalizedString(#"Save", nil)
style:UIBarButtonItemStylePlain
target:self
action:#selector(performAddNewDay:)];
self.saveButton = newSaveButton;
[newSaveButton release];
The performAddNewDay method within the UIButtonItem looks like this:
- (void) performAddNewDay:(id)paramSender{
SelfEsteemBldrAppDelegate *delegate = (SelfEsteemBldrAppDelegate *)
[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = delegate.managedObjectContext;
NSLog(#"Past init point in PerformAddNewDay");
// Get the values from the text fields
NSInteger dailyRatingAsInteger = [self.textFieldDailyRating.text integerValue];
NSNumber *ddailyRating = [NSNumber numberWithInteger:dailyRatingAsInteger];
NSLog(#"Daily Rating Entered is %#", ddailyRating);
NSString *ddailyAccomplishment = self.textFieldAccomplishment.text;
NSLog(#"Daily Accomplishment Entered is %#", ddailyAccomplishment);
NSInteger dailyAccompRatingAsInteger = [self.textFieldAccomplishmentRating.text integerValue];
NSNumber *ddailyAccompRating = [NSNumber numberWithInteger:dailyAccompRatingAsInteger];
NSLog(#"Daily Accomp Rating Entered is %#", ddailyAccompRating);
// Create a new instance of Day
Day *newDay = [NSEntityDescription
insertNewObjectForEntityForName:#"Day"
inManagedObjectContext:context];
if (newDay != nil){
// Set the properties according to the values we retrieved from the text fields
newDay.dailyAccomp = ddailyAccomplishment;
newDay.dailyRating = ddailyRating;
newDay.dailyAccompRating = ddailyAccompRating;
NSError *savingError = nil;
// Save the new day
if ([context save:&savingError] == YES){
// If successful, simply go back to the previous screen
[self.navigationController popViewControllerAnimated:YES];
} else {
// If we failed to save, display a message
[self
displayAlertWithTitle:NSLocalizedString(#"Saving", nil)
message:NSLocalizedString(#"Failed to save the context", nil)];
}
} else {
// We could not insert a new Day managed object
[self
displayAlertWithTitle:NSLocalizedString(#"New Day", nil)
message:NSLocalizedString(#"Failed To Insert A New Day", nil)];
}
}
I've commented out most of the code to try to find the offending statement, and it seems to be
**NSManagedObjectContext *context = delegate.managedObjectContext;**
That is, if I comment everything below and including this stmnt, app doesn't crash. It doesn't do anything, it just "waits" (as expected). If I uncomment this stmnt, app crashes. SelfEsteemBldrAppDelegate is also imported using the #import "SelfEsteemBldrAppDelegate.h' stmnt.
Again, the error that I'm getting is in the Core Data Stack persistentStoreCoordinator method within SelfEsteemBldrAppDelegate.m. The crash occurs at the following:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"SelfEsteemBldr.sqlite"];
The error that I'm given is
[NSPathStore2 URLByAppendingPathComponent:]: unrecognized selector sent to instance 0x4d6f440
So, after all that, any ideas as to why I might be getting this message, and what I can do to resolve it? From what I understand, I'm not supposed to interact with the Core Data Stack methods, so I don't want to fool around with that code. Any help would be greatly appreciated. Also, if I've left out any info you may need, please let me know. Thanks.
Normally, I faced this problem and encountered that this error comes when managedObjectContext is nil... To solve this i have used below code and worked fine for me..
if (managedObjectContext == nil) {
managedObjectContext = [(iBountyHunterAppDelegate *) [[UIApplication sharedApplication] delegate] managedObjectContext];
}

NSInvalidArgumentException: Illegal attempt to establish a relationship between objects in different contexts

I have an app based on the CoreDataBooks example that uses an addingManagedObjectContext to add an Ingredient to a Cocktail in order to undo the entire add. The CocktailsDetailViewController in turn calls a BrandPickerViewController to (optionally) set a brand name for a given ingredient. Cocktail, Ingredient and Brand are all NSManagedObjects. Cocktail requires at least one Ingredient (baseLiquor) to be set, so I create it when the Cocktail is created.
If I add the Cocktail in CocktailsAddViewController : CocktailsDetailViewController (merging into the Cocktail managed object context on save) without setting baseLiquor.brand, then it works to set the Brand from a picker (also stored in the Cocktails managed context) later from the CocktailsDetailViewController.
However, if I try to set baseLiquor.brand in CocktailsAddViewController, I get:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason:
'Illegal attempt to establish a
relationship 'brand' between objects
in different contexts'
From this question I understand that the issue is that Brand is stored in the app's managedObjectContext and the newly added Ingredient and Cocktail are stored in addingManagedObjectContext, and that passing the ObjectID instead would avoid the crash.
What I don't get is how to implement the picker generically so that all of the Ingredients (baseLiquor, mixer, garnish, etc.) can be set during the add, as well as one-by-one from the CocktailsDetailViewController after the Cocktail has been created. In other words, following the CoreDataBooks example, where and when would the ObjectID be turned into the NSManagedObject from the parent MOC in both add and edit cases? -IPD
UPDATE - Here's the add method:
- (IBAction)addCocktail:(id)sender {
CocktailsAddViewController *addViewController = [[CocktailsAddViewController alloc] init];
addViewController.title = #"Add Cocktail";
addViewController.delegate = self;
// Create a new managed object context for the new book -- set its persistent store coordinator to the same as that from the fetched results controller's context.
NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init];
self.addingManagedObjectContext = addingContext;
[addingContext release];
[addingManagedObjectContext setPersistentStoreCoordinator:[[fetchedResultsController managedObjectContext] persistentStoreCoordinator]];
Cocktail *newCocktail = (Cocktail *)[NSEntityDescription insertNewObjectForEntityForName:#"Cocktail" inManagedObjectContext:self.addingManagedObjectContext];
newCocktail.baseLiquor = (Ingredient *)[NSEntityDescription insertNewObjectForEntityForName:#"Ingredient" inManagedObjectContext:self.addingManagedObjectContext];
newCocktail.mixer = (Ingredient *)[NSEntityDescription insertNewObjectForEntityForName:#"Ingredient" inManagedObjectContext:self.addingManagedObjectContext];
newCocktail.volume = [NSNumber numberWithInt:0];
addViewController.cocktail = newCocktail;
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addViewController];
[self.navigationController presentModalViewController:navController animated:YES];
[addViewController release];
[navController release];
}
and here's the site of the crash in the Brand picker (this NSFetchedResultsController is backed by the app delegate's managed object context:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
if ([delegate respondsToSelector:#selector(pickerViewController:didFinishWithBrand:forKeyPath:)])
{
[delegate pickerViewController:self
didFinishWithBrand:(Brand *)[fetchedResultsController objectAtIndexPath:indexPath]
forKeyPath:keyPath]; // 'keyPath' is #"baseLiquor.brand" in the crash
}
}
and finally the delegate implementation:
- (void)pickerViewController:(IngredientsPickerViewController *)pickerViewController
didFinishWithBrand:(Brand *)baseEntity
forKeyPath:(NSString *)keyPath
{
// set entity
[cocktail setValue:ingredient forKeyPath:keyPath];
// Save the changes.
NSError *error;
if (![cocktail.managedObjectContext save:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
// dismiss picker
[self.navigationController popViewControllerAnimated:YES]
}
EVEN MORE
I'm making progess based on Marcus' suggestions -- I mapped the addingManagedObjectContexts to the parent managedObjectContext and wrapped everything in begin/endUndoGrouping to handle cancel vs. save.
However, the object to be created is in an NSFetchedResultsController, so when the user hits the "+" button to add the Cocktail, the (possibly-to-be-undone) entity briefly appears in the table view as the modal add view controller is presented. The MDN example is Mac-based so it doesn't touch on this UI behavior. What can I do to avoid this?
Sounds like you are creating two different Core Data stacks (NSManagedObjectContext, NSManagedObjectModel, and NSPersistentStoreCoordinator). What you want to do from the example is just create two NSManagedObjectContext instances pointing at the same NSPersistentStoreCoordinator. That will resolve this issue.
Think of the NSManagedObjectContext as a scratch pad. You can have as many as you want and if you throw it away before saving it, the data contained within it is gone. But they all save to the same place.
update
The CoreDataBooks is unfortunately a really terrible example. However, for your issue, I would suggest removing the creation of the additional context and see if the error occurs. Based on the code you posted and I assume the code you copied directly from Apple's example, the double context, while virtually useless, should work fine. Therefore I suspect there is something else at play.
Try using a single context and see if the issue persists. You may have some other interesting but subtle error that is giving you this error; perhaps a overrelease somewhere or something along those lines. But the first step is to remove the double context and see what happens.
update 2
If it is crashing even with a single MOC then your issue has nothing to do with the contexts. What is the error you are getting with a single MOC? When we solve that, then we will solve your entire issue.
As for a better solution, use NSUndoManager instead. That is what it is designed for. Apple REALLY should not be recommending multiple MOCs in their example.
I answered a question on here recently about using the NSUndoManager with Core Data but you can also look at some of my articles on the MDN for an example.
Yeah, you definitely don't want to cross context boundaries when setting relationships between objects; they both need to be in the same NSManagedObjectContext. In the old EOF, there were APIs for faulting objects into different contexts, but doesn't look like CoreData has an equivalent.

Reloading TTableView after DataSource items are added

This is the code of my initializer:
if (self = [super init]) {
self.title = #"Posts";
self.variableHeightRows = YES;
//XLog("");
PostsDataSource *dataSource = [[[PostsDataSource alloc] init] autorelease];
[dataSource.delegates addObject:self];
[dataSource load:TTURLRequestCachePolicyMemory nextPage:NO];
self.dataSource = dataSource;
}
return self;
In my datasource I'm doing a TTURLRequest and when requestDidFinishLoad gets called, my datasource gets filled with some items.
This all works quite good, but my TTableViewController doesn't show any of these files because it gets initialised and displayed before my datasource is finished. I know it works, because caching my datasource to disk shows all items.
The question is: How do I tell my TTableViewController to refresh the data out of my datasource file in my "requestDidFinishLoad" ?
Is your datasource bound to a TTURLRequestModel? If so, you may be missing a call to:
[super requestDidFinishLoad:request];
If it's bound to a base TTModel, you may be missing a call to:
[self didFinishLoad];
These should happen in your requestDidFinishLoad: method.
Update
Didn't realize you weren't using a TTModel. Does your requestDidFinishLoad call:
[self dataSourceDidFinishLoad];
Update again based on comments below
The documentation or tutorial you were reading is way out of date and newer version of Three20 no longer work this way. There is a great tutorial at http://three20.info/tutorials/github which should get you back on the right track.