Thursday, January 21, 2010
Coming Soon… One Week with Android
IPhone App Yelp is looking forward to the iPhones newest version

With updated version this app acquired such features as user-reviewed listings for restaurants, bars and banks etc. using iPhone’s GPS connection for reaching your friends.
In addition this app contains a new augmented reality feature called “Monocle”. It shows you what’s nearby when you turn round with your iphone’s camera. These kinds of applications being under development wait for the release of the iPhone OS 3.1 to officially enable the feature. This explains why the augmented reality feature in the Yelp application was included as a hidden Easter egg to avoid detection by Apple's reviewers.
As soon as you download an updated Yelp app (which is by the way FREE) you must shake your iPhone three times to activate a feature “Monocle”. A message in form of a blue box will let you know if the feature has been activated. When the button appears in the top right corner you will be able to use it. But you should take into consideration that it works only with iPhone 3GS.
Wednesday, January 20, 2010
Another TableView / NSFetchedResultsController Gotcha
Since More iPhone 3 Development was released, I've been getting sporadic reports of a problem with the Chapter 4 version of the Core Data application that, until last night, I hadn't been able to reproduce. One reader was finally able to send me specific instructions, and lo and behold, I was able to reproduce the problem.
So, I started stepping through the code, and found that in certain situations (the parameters of which, I haven't fully figured out yet), my code is attempting to insert two sections in the table when only one new section is required by the update. It happens when a value used in the section key path is changed, but not always when that happens.
What happens is, in controller:didChangeSection:atIndex:forChangeType:, I get notified of a new section being inserted into the fetched results controller and insert a corresponding section at the appropriate spot in the table, like so:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
All well and good, right? But then, in controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: which fires afterwards, I have code that checks to make sure the number of sections matches between the fetched results controller and the table view. This code is necessary because in some situations, NSFetchedResultController doesn't tell its delegate if a new section was created. It's a pretty simple check, I just find the number of sections in the fetched results controller and in the table and when they don't match, I insert a new section in the table.
NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (frcSectionCount != tableSectionCount)
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];
And this works most of the time. But, sometimes it doesn't. The sporadic nature makes it hard to debug, but I finally managed to step through the code when it was happening. In controller:didChangeSection:atIndex:forChangeType:, before the line of code that inserts a new section, I checked the number of sections in the table view. There were five.
Then, after the line of code that inserted the section, I checked again. There were still five.
Sounds like a bug in Apple's code, right? Actually, it's not. It's documented behavior.
The documentation for insertRowsAtIndexPath:withRowAnimation: on UITableView says:
UITableView defers any insertions of rows or sections until after it has handled the deletions of rows or sections. This happens regardless of ordering of the insertion and deletion method calls.This leaves me with quite a conundrum. Since my code is not directly managing the table, but NSFetchedResultsController is deferring certain tasks to its delegate which is my code, I don't have an easy way (that I know of yet) to determine when the row insertion from the earlier code is going to be deferred hence causing my later check to fail.
One solution, which feels kludgey, would be to have a BOOL instance variable to track when an earlier delegate method call inserted a row. I don't like that solution, though, so I'm looking for a better option to incorporate into my generic delegate methods.
I'll keep you all updated on my progress, but if you have any ideas how I can determine if there is a pending insert in a table, feel free to share them in the comments.
Update 1: There is a private mutable array called _insertItems that holds the deferred insertions. Even though it's published in the header file, I think accessing this directly would technically be considered use of a private API. Instance variables with an underscore are considered private by Apple, even if published in a header file.
Update 2: I have an illicit functioning version! Unfortunately, I can't use it because it requires accessing private instance variables of UITableView. Once Apple's Bug Reporter is back up, I'm going to put in an enhancement request to have the information I need made public, but I'm probably going to have to come up with a different interim solution, and it will probably be hacky.
For the curious, what I did was to create a category on UITableView that added this method:
- (NSUInteger)numberOfPendingSectionInserts
{
NSUInteger ret = 0;
for (id /* UIUpdateItem */ oneUpdateItem in _insertItems)
{
if ([oneUpdateItem isSectionOperation])
ret++;
}
return ret;
}
Now, don't use this in your apps, as you will get rejected from the app store. UIUpdateItem is not a public class, and _insertItems is not a public instance variable (though it's contained in a public header file). Were this information to be made available, then I would be able to do a more robust consistency check that would eliminate the double insertion problem:
NSUInteger tableSectionCount = [self.tableView numberOfSections] + [self.tableView numberOfPendingSectionInserts];
NSUInteger frcSectionCount = [[controller sections] count];
if (frcSectionCount != tableSectionCount)
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];