Adding Sections to Core Data Recipes - Crash

Discussion in 'iOS Programming' started by penjetti, Sep 28, 2009.

  1. penjetti macrumors newbie

    Joined:
    Sep 27, 2009
    #1
    Hello. I am a newbie, so please excuse my lack of correct terminology. I have code similar to Core Data Recipes, where a category is selected on the detail view. I thought that my problem was being caused because I was trying to display category section headings on the root view, so I changed some data in Recipes and was able to replicate the problem. Basically, I crash every time I try to add a new row, either on the selection of the category or on the save. The error is:

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSManagedObject compare:]: unrecognized selector sent to instance 0x3e4e140'

    It seems like if I choose a category that has already been selected for an existing recipe, everything goes well. But if I even select a "new" category, crash. I have tried a ton of things from the internet to no avail. But like I said, I am new at this. I thought someone might be able to download the code from the website and change what I changed to reproduce. I'm sure I am missing something obvious. Please help!

    To modify Recipe, I added numberOfSectionsInTableView and titleForHeaderInSection. I altered fetchedResultsController sort descriptors and section name key path. That's it, I'm pretty sure. I think that this code had problems before I got my hands on it... but I would really appreciate it if anyone knows how to get it working.

    Here is the link to download the "pristine" code from Apple:
    http://developer.apple.com/iphone/library/samplecode/iPhoneCoreDataRecipes/index.html


    Here is the modified code from RecipeListTableViewController.m:


    Code:
    #pragma mark -
    #pragma mark Table view methods
    
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        NSInteger count = [[fetchedResultsController sections] count];
        
    	if (count == 0) {
    		count = 1;
    	}
    	
        return count;
    }
    
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        NSInteger numberOfRows = 0;
    	
        if ([[fetchedResultsController sections] count] > 0) {
            id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
            numberOfRows = [sectionInfo numberOfObjects];
        }
        
        return numberOfRows;
    	
    }
    
    
    
    
    
    // NOT IN RECIPE or CORE DATA LOCATION TUTORIAL
    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    	// Display the types' names as section headings.
    	//    return [[[fetchedResultsController sections] objectAtIndex:section] valueForKey:@"name"];
    	
    	if (fetchedResultsController.sections.count > 0) {
    		id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    		return [sectionInfo name];
    	}
    
    
    }
    
    
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        // Dequeue or if necessary create a RecipeTableViewCell, then set its recipe to the recipe for the current row.
        static NSString *RecipeCellIdentifier = @"RecipeCellIdentifier";
        
        RecipeTableViewCell *recipeCell = (RecipeTableViewCell *)[tableView dequeueReusableCellWithIdentifier:RecipeCellIdentifier];
        if (recipeCell == nil) {
            recipeCell = [[[RecipeTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:RecipeCellIdentifier] autorelease];
    		recipeCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        }
        
    	[self configureCell:recipeCell atIndexPath:indexPath];
        
        return recipeCell;
    }
    
    
    - (void)configureCell:(RecipeTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
        // Configure the cell
    	Recipe *recipe = (Recipe *)[fetchedResultsController objectAtIndexPath:indexPath];
        cell.recipe = recipe;
    }
    
    
    
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    	Recipe *recipe = (Recipe *)[fetchedResultsController objectAtIndexPath:indexPath];
        
        [self showRecipe:recipe animated:YES];
    }
    
    
    // Override to support editing the table view.
    - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
        if (editingStyle == UITableViewCellEditingStyleDelete) {
            // Delete the managed object for the given index path
    		NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
    		[context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];
    		
    		// Save the context.
    		NSError *error;
    		if (![context save:&error]) {
    			/*
    			 Replace this implementation with code to handle the error appropriately.
    			 
    			 abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
    			 */
    			NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    			abort();
    		}
    	}   
    }
    
    
    #pragma mark -
    #pragma mark Fetched results controller
    
    - (NSFetchedResultsController *)fetchedResultsController {
        // Set up the fetched results controller if needed.
        if (fetchedResultsController == nil) {
            // Create the fetch request for the entity.
            NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
            // Edit the entity name as appropriate.
            NSEntityDescription *entity = [NSEntityDescription entityForName:@"Recipe" inManagedObjectContext:managedObjectContext];
            [fetchRequest setEntity:entity];
            
            // Edit the sort key as appropriate.
            NSSortDescriptor *typeDescriptor = [[NSSortDescriptor alloc] initWithKey:@"type" ascending:YES];
    		NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
    
            NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:typeDescriptor, nameDescriptor, nil];
    		        
            [fetchRequest setSortDescriptors:sortDescriptors];
            
            // Edit the section name key path and cache name if appropriate.
            // nil for section name key path means "no sections".
            NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"type.name" cacheName:@"Root"];
    		
    		
            aFetchedResultsController.delegate = self;
            self.fetchedResultsController = aFetchedResultsController;
            
            [aFetchedResultsController release];
            [fetchRequest release];
            [typeDescriptor release];
    		[nameDescriptor release];
            [sortDescriptors release];
        }
    	
    	return fetchedResultsController;
    }  
     
  2. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #2
    Welcome aboard, penjetti! And don't worry. We can hopefully provide some direction to get you using the correct terminology, if necessary.

    Can you explain this further? Because the sample app doesn't contain the ability to specify new categories. Perhaps you forgot to include some code that added this ability.
    EDIT: Ah, I can now reproduce the error you're getting. It is definitely related to the addition of "type" to the sort descriptors. I believe the error is caused by the fact that "type" is an NSManagedObject in Recipe and, therefore, does not have a compare: method.

    Also, your tableView:titleForHeaderInSection: generates a warning about "Control reaches end of non-void function". This is because, if fetchedResultsController.sections.count == 0, you get to the end of the method without any return statement. And since the method returns a non-void, NSString* in this case, you should make sure it returns something in this case too. I believe a nil would be sufficient.

    P.S. Oh, and thanks for using code tags right away! Takes other newbies a while before they get into that habit, if ever.
     
  3. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #3
    Try this line instead:
    Code:
    NSSortDescriptor *typeDescriptor = [[NSSortDescriptor alloc] initWithKey:@"type.name" ascending:YES];
     
  4. penjetti thread starter macrumors newbie

    Joined:
    Sep 27, 2009
    #4
    Thank you Dejo!!!!

    Dejo - thanks so much for the advice (and the encouragement). It worked!!! I have spent no less than 5 days (well, nights) trying to figure this out. Guess that shows you how much of a newbie I am : ) Thank you again, so so so much!!!
     
  5. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #5
    You're quite welcome. Do you understand how the change fixes things? If so, good. If not, let me know and I'll try to explain it further.
     
  6. penjetti thread starter macrumors newbie

    Joined:
    Sep 27, 2009
    #6
    Thanks Dejo. I understand *to an extent*, since I fixed a previous error by adding ".name":

    Code:
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"type.name" cacheName:@"Root"];
    I was trying to isolate the error and playing around when I fixed that (and surprised myself!). I should have taken it one step further!!

    That being said, if you would like to give me more technical information for why that was the correct thing to do, I would appreciate it. I am on a steep learning curve and appreciate all the information I can get!

    Thanks again!!!!
     
  7. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #7
    Sure, I'll give it a go.

    As described in the Class Reference for NSSortDescriptor:
    Because you didn't specify a comparison selector when you defined your sort descriptors, it uses the plain old compare: method on the objects. But NSManagedObjects, which is what type is an object of, don't have a built-in compare: method. Therefore the run-time error.

    By using a sort key of @"type.name", you are now asking it to compare via the name property of the type object. Since name is an NSString, it does have a compare: method, and so the sort descriptor is able to use it. And, in the end, that's really what you wanted to be using to do the sort anyways, so, all's good.

    The thing that strikes me strange still about the previous, error-producing code was that it didn't complain when sorting the data for initial display; it only complained when trying to add a new element. If anybody else has any insight as to why that might be, I (and penjetti too, I'm sure) would love to hear it.
     
  8. penjetti thread starter macrumors newbie

    Joined:
    Sep 27, 2009
    #8
    Dejo,

    Thank you so much for posting that information. I must admit, I am still trying to wrap my head around object oriented programming in general.. what you say makes sense, but I have to still really think through "what is an object? what is a context? what is an entity?" I guess everyone had to learn at some point. In any case, I am now having an error that I'm sure is related, from the same code (Core Data Recipes). I tried doing what you suggested there, but it must not be the same since it didn't solve the problem.

    I have isolated the problem to these lines from RecipeDetailViewController.m:

    Code:
    - (void)viewWillAppear:(BOOL)animated {
        
        [super viewWillAppear:animated];
    	
        [photoButton setImage:recipe.thumbnailImage forState:UIControlStateNormal];
    	self.navigationItem.title = recipe.name;
        nameTextField.text = recipe.name;    
        overviewTextField.text = recipe.overview;    
        prepTimeTextField.text = recipe.prepTime;    
    	[self updatePhotoButton];
    
    	/*
    	 Create a mutable array that contains the recipe's ingredients ordered by displayOrder.
    	 The table view uses this array to display the ingredients.
    	 Core Data relationships are represented by sets, so have no inherent order. Order is "imposed" using the displayOrder attribute, but it would be inefficient to create and sort a new array each time the ingredients section had to be laid out or updated.
    	 */
    	NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"displayOrder" ascending:YES];
    	NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:&sortDescriptor count:1];
    	
    	NSMutableArray *sortedIngredients = [[NSMutableArray alloc] initWithArray:[recipe.ingredients allObjects]];
    	[sortedIngredients sortUsingDescriptors:sortDescriptors];
    	self.ingredients = sortedIngredients;
    
    	[sortDescriptor release];
    	[sortDescriptors release];
    	[sortedIngredients release];
    	
    	// Update recipe type and ingredients on return.
        [self.tableView reloadData]; 
    }
    Specifically, I think, the problem is in this section:
    Code:
    	NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"displayOrder" ascending:YES];
    	NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:&sortDescriptor count:1];
    	
    	NSMutableArray *sortedIngredients = [[NSMutableArray alloc] initWithArray:[recipe.ingredients allObjects]];
    	[sortedIngredients sortUsingDescriptors:sortDescriptors];
    	self.ingredients = sortedIngredients;
    Again, in the "pristine" sample code from Apple's own website, whenever I add a new ingredient and save the recipe, then immediately click on that recipe from the RecipeTableView, there is a crash. I tried to make the sort descriptor "ingredient.displayOrder" instead of just "displayOrder". I also tried to change it to "name" and "ingredient.name". All to no avail. I think maybe a memory issue is involved? I know I am probably making some sort of ridiculous newbie error. But I'll tell ya, it is hard to learn when Apple's own sample code is so buggy!! Thanks in advance if you can help, and thanks for taking all the time you have already.
     
  9. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #9
    Yeah, looks like there is a bug with the iPhoneCoreDataRecipes code. I'd suggest you submit a bug report.
     
  10. penjetti thread starter macrumors newbie

    Joined:
    Sep 27, 2009
    #10
    Yeah, I guess I should. I was kind of hoping for time's sake that someone could take a look at it, but I guess that would be more for my time's sake than anyone else's!

    Thanks, and have a good one.
    Jen
     
  11. student8080 macrumors newbie

    Joined:
    Nov 23, 2009
    #11
    Still having error

    Hi Penjetti,

    I am trying to add a section to recipe like you did. I still face the same error even after changing to "type.name".

    Is it possible to ask you to email me your source code to leelim81@gmail.com ?


     

Share This Page