views:

5293

answers:

10

I'm writing my first iPhone/Cocoa app. It has two table views inside a navigation view. When you touch a row in the first table view, you are taken to the second table view. I would like the second view to display records from the CoreData entities related to the row you touched in the first view.

I have the CoreData data showing up fine in the first table view. You can touch a row and go to the second table view. I'm able to pass info from the selected object from the first to the second view. But I cannot get the second view to do its own CoreData fetching. For the life of me I cannot get the managedObjectContext object to pass to the second view controller. I don't want to do the lookups in the first view and pass a dictionary because I want to be able to use a search field to refine results in the second view, as well as insert new entries to the CoreData data from there.

Here's the function that transitions from the first to the second view.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // Navigation logic may go here -- for example, create and push another view controller.
    NSManagedObject *selectedObject = [[self fetchedResultsController] objectAtIndexPath:indexPath];
    SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:@"SecondView" bundle:nil];

    secondViewController.tName = [[selectedObject valueForKey:@"name"] description];
    secondViewController.managedObjectContext = [self managedObjectContext];

    [self.navigationController pushViewController:secondViewController animated:YES];
    [secondViewController release];
}

And this is the function inside SecondViewController that crashes:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.title = tName;

    NSError *error;
    if (![[self fetchedResultsController] performFetch:&error]) { // <-- crashes here
     // Handle the error...
    }
}

- (NSFetchedResultsController *)fetchedResultsController {

    if (fetchedResultsController != nil) {
        return fetchedResultsController;
    }

    /*
     Set up the fetched results controller.    
     */
    // Create the fetch request for the entity.
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
        // **** crashes on the next line because managedObjectContext == 0x0
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"SecondEntity" inManagedObjectContext:managedObjectContext]; 
    [fetchRequest setEntity:entity];

    // <snip> ... more code here from Apple template, never gets executed because of the crashing

    return fetchedResultsController;
}

Any ideas on what I am doing wrong here?

managedObjectContext is a retained property.

UPDATE: I inserted a NSLog([[managedObjectContext registeredObjects] description]); in viewDidLoad and it appears managedObjectContext is being passed just fine. Still crashing, though.

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+entityForName: could not locate an NSManagedObjectModel for entity name 'SecondEntity''

A: 

Are you sure there's an entity called "SecondEntity"? (That would be the straightforward way to interpret the error message.)

However, if this is a master list -> detail view type interaction, I would suggest passing the selected object directly to the second view controller, rather than giving it just the "tname". This object presumably contains everything you need to populate the second table directly via its properties.

That way, you don't actually do any explicit fetching in the second view controller. I.e:

@interface SecondViewController : UITableViewController
@property (nonatomic, retain) NSManagedObject *selectedObject;
@end

@implementation SecondViewController
- (void)viewDidLoad
{
    NSMutableArray *stuff = [[[selectedObject valueForKey:@"aToManyRelationship"] allObjects] mutableCopy];
    // sort stuff the way you want to display them, etc.
}
...
Daniel Dickison
Yes, the entity exists.It's not master list->detail.. it's more like category->items, and I want to be able to interact directly with the data store, because the second view is really the main interaction screen, so I don't want to just pass a snapshot to the second view.
amo
A: 

Just for kicks.. try replacing:

NSEntityDescription *entity = [NSEntityDescription entityForName:@"SecondEntity" inManagedObjectContext:managedObjectContext];

with:

NSEntityDescription *entity = [NSEntityDescription entityForName:@"SecondEntity" inManagedObjectContext:[self managedObjectContext]];

You are setting managedObjectContext through a setter, and then trying to access the ivar directly. Depending on how your properties are defined, this may not be correct. [self managedObjectContext] will try to access the value through the getter, rather than directly.

mmc
Interesting. I will try this when I get home.
amo
So does that mean self.title = tName is also bad form?
amo
Not at all. Although I am not sure where tName is coming from. dot syntax in Objective C *is* calling your accessor methods, even though it may *appear* to be hitting ivars.
mmc
+1  A: 

Oh, this is interesting. I spent some quality time with the stack trace and I think I've got it figured out.

So pushViewController calls viewDidLoad not once, but twice. The first time it calls viewDidLoad, the objects appear to not be instantiated properly. The second time, they are. So the first time this code runs it can't access managedObjectContext and it throws an exception. The second time it runs, everything's fine. No crash.

There are a lot of references to problems with viewDidLoad executing multiple times on Google, so I think the solution is to not do this fetch request initialization in viewDidLoad.

amo
A: 

Did you find a way to fix this? Where did you run the fetch init?

Shades
I wrapped the code in if([self managedObjectContext]) so it would not attempt to init the fetch request if it was going to crash. The fetch init is still in viewDidLoad, but now it only runs once.
amo
A: 

I've been struggling with the same issue and I'm still just a newbie. I think I figured out what is going on. Let me know if this makes sense.

In short you are trying to fetch an entity from an objectContext that hadn't been set up yet. Your options therefore are to set it up right then or do elsewhere in the app before this view loads.

If your app is setup like the CoreDataBooks app demo from the iphone dev center with a main UIApplicationDelegate also managing the CoreData stack, then you should be able to do the following:

if (managedObjectContext == nil) { managedObjectContext = [[[UIApplication sharedApplication] delegate] managedObjectContext]; }

This should do the trick.

aryeh
A: 

Hi, I 'fixed' similar problem using Aryeh's post;

if (managedObjectContext == nil) { managedObjectContext = [[[UIApplication sharedApplication] delegate] managedObjectContext]; }

However I get a warning: '-managedObjectContext' not found in protocol(s).

Anyone have an idea what's causing this? I'm stumped.

Any help appreciated. Thanks

A: 

One thing for sure, the line: secondViewController.managedObjectContext = [self managedObjectContext];

Should be: secondViewController.managedObjectContext = self.managedObjectContext;

Unless the current object implements a method called 'managedObjectContext' which returns that variable.

Mr. Cairo
A: 

This might be the problem.

short answer: delete your app then run it again.

long answer:

If you build and run your project, CoreData will save your model to wherever you said to (persistent store location).

If you change something in the CoreData model, when you run the app again the new model will not match the saved model giving you that error.

To fix it, delete your app. This will get rid of the saved model and when you run it again, it will recreate your new model.

jasongregori
+5  A: 

You can suppress the '-managedObjectContext' not found in protocols warning by casting your application delegate first:

if (managedObjectContext == nil) { managedObjectContext = [(MyAppDelegateName *)[[UIApplication sharedApplication] delegate] managedObjectContext]; }
Sascha Konietzke
A: 

I had this problem and found that my object's init message was accessing the managedObjectContext before the setManagedObjectContext was being called...

Before:

 dataController = [[DataController alloc] init];
 [dataController setManagedObjectContext:[self managedObjectContext]];

After:

 dataController = [DataController alloc];
 [dataController setManagedObjectContext:[self managedObjectContext]];            
 [dataController init];

feh. Rookie error.

Chad Celsius