Showing posts with label Project Template. Show all posts
Showing posts with label Project Template. Show all posts

Wednesday, December 30, 2009

Updated Navigation-Based Core Data Application Template

Periodically, while working on the Core Data chapters in More iPhone 3 Development, I would update my copy of Apple's provided Navigation-Based Core Data Application project template with logic to handle different common scenarios. While working on the previous blog post, I made some more tweaks to the template to better handle object changes that trigger managed objects to move between sections when specifying a sectionNameKeyPath. You can find the most recent version of this project template here, and I've included the four delegate methods from the template at the end of this blog post.

I've really tried to give NSFetchedResultsController the benefit of the doubt. It's a really, really great concept and when it works, I really like it, but the execution is way below Apple's normal level of code quality. All of the code I've added to this template is needed simply to handle typical use-cases supposedly supported by this class… things that should be handled out of the box without any need for additional code. In this particular case, the delegate methods should really only be required for handling extraordinary or unusual scenarios.

If I have time, I may try to encapsulate this logic into a subclass of NSFetchedResultsController, but for the time being, if you use this template or the delegate methods at the end of this posting, it should take care of the bulk of situations for you automatically. I suspect that there will be a few more tweaks to this code as I use it in real-world code, but if there are, I'll post the changes and a new version of the template here. In any case, this template is a far better starting point than Apple's provided template and may just keep you from pulling all your hair out.

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate: {
NSString *sectionKeyPath = [controller sectionNameKeyPath];
if (sectionKeyPath == nil)
break;
NSManagedObject *changedObject = [controller objectAtIndexPath:indexPath];
NSArray *keyParts = [sectionKeyPath componentsSeparatedByString:@"."];
id currentKeyValue = [changedObject valueForKeyPath:sectionKeyPath];
for (int i = 0; i < [keyParts count] - 1; i++) {
NSString *onePart = [keyParts objectAtIndex:i];
changedObject = [changedObject valueForKey:onePart];
}

sectionKeyPath = [keyParts lastObject];
NSDictionary *committedValues = [changedObject committedValuesForKeys:nil];

if ([[committedValues valueForKeyPath:sectionKeyPath] isEqual:currentKeyValue])
break;

NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (tableSectionCount != frcSectionCount) {
// Need to insert a section
NSArray *sections = controller.sections;
NSInteger newSectionLocation = -1;
for (id oneSection in sections) {
NSString *sectionName = [oneSection name];
if ([currentKeyValue isEqual:sectionName]) {
newSectionLocation = [sections indexOfObject:oneSection];
break;
}

}

if (newSectionLocation == -1)
return; // uh oh

if (!((newSectionLocation == 0) && (tableSectionCount == 1) && ([self.tableView numberOfRowsInSection:0] == 0)))
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:newSectionLocation] withRowAnimation:UITableViewRowAnimationFade];
NSUInteger indices[2] = {newSectionLocation, 0};
newIndexPath = [[[NSIndexPath alloc] initWithIndexes:indices length:2] autorelease];
}

}

case NSFetchedResultsChangeMove:
if (newIndexPath != nil) {

NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (frcSectionCount >= tableSectionCount)
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];
else
if (tableSectionCount > 1)
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationNone];


[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath]
withRowAnimation: UITableViewRowAnimationRight
]
;

}

else {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationFade];
}

break;
default:
break;
}

}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {

case NSFetchedResultsChangeInsert:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1)))
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1) ))
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
break;
case NSFetchedResultsChangeUpdate:
break;
default:
break;
}

}

Wednesday, November 11, 2009

Template Updated Again

I've updated the Navigation-Based Core Data Application Xcode project with the logic from my last post to handle the section problem. As before, unzip and copy the folder to this location, replacing the one that's there:

/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application/Navigation-based Application/

I've submitted this to Apple in a bug report, but I don't know if they'll update the official template. They may not want something quite this complex in the template, but until they resolve the underlying problem, I want to make this available. So here it is.

Navigation-based Application Core Data Template

I've updated the Navigation-Based Core Data Application Xcode project to include a suggested workaround to a problem in NSFetchedResultsController. I believe this problem only existed in SDK 3.0 and was later fixed, but for backwards compatibility, I've added the workaround to the project template for anyone interested in supporting 3.0 users.

As before, unzip and copy the folder to this location, replacing the one that's there:

/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application/Navigation-based Application/
Note: I updated the project yet again, at 1:11 pm Eastern time, to deal with a problem with inserting the first object when using a section name key path. Because tables can't have zero sections, this new version checks to make sure before inserting a new section, that it isn't an empty table getting its first row. In that case, the table will already have one section and doesn't need a second. Adding a second in that scenario causes a crash.
Note Again: Another update to the project at 10:21 eastern time on November 11. Fixed a few more issues I found. I hope this will be the last version.
Note Yet Again: I forgot to move the update into the right folder on the web server, so the 10:21 eastern time update actually happened at 10:59. Re-download if you grabbed it between those times.



Thanks to Simon Wolf for catching it