Friday, January 29, 2010

Waaaaah!

Oooh, a petition. I'm sure this will change something.

No I don't.

This modified re-tweet gave me a laugh, though.



Future Shock

Go read Frasier Spear's Future Shock.



Failure to Think Different

I'm not even going to say very much in response to this blog post, except to point out that her sense of self-importance is way out of proportion. Apple doesn't have to convince her that the iPad is "better than a netbook" because it's not about her. This isn't a product primarily targeted at techies, tech reporters, developers, gurus, pundits, or the like. Much of the target audience doesn't even grok what a netbook is.

I'm sure somebody has told you all this before, but let me point it out again: it's not always about you. Products can be successful even if they aren't right for you.

Frankly, I think this thing could be a huge success just from techies buying them for their parents or other relatives who rely on them for tech support.

This thing doesn't cost all that much more than a portable DVD player, for crying out loud, and it's smaller. I'd buy one just to watch movies when I travel.

I'm a techie, but I don't need to be able to program on every electronic device I own. I don't hate my dishwasher because I can't get to the command line. I don't hate my DVD player because it runs a proprietary operating system. Sheesh.



Adobe's Unhappy

I was going to respond to Adobe's self-serving, biased, and not entirely factually accurate blog post, but rather than incur more wrath from the legion of Flash "developers", I'm just going to link to Chris Rawson's piece at TUAW.

Regardless of how the iPad does, with over 70 million iPhone OS devices in the world that don't run Flash, content providers who currently use depend on Flash HAVE to be looking at other options. In many cases, the other option will be an alternative instead of completely supplanting Adobe, but either way, it's good for the web.



Thursday, January 28, 2010

Apple is Hiring Cocoa Touch Programmers

In the last few hours, I've seen tweets from at least two different folks at Apple about new openings for engineers for the iPhone, iPod Touch, and iPad. Here's one. Here are some more.

Or you could just do your own search on Apple's job's site.

Also, if you're more of an East Coast kinda person, Rosetta Stone is also looking for a good iPhone programmer. Ping me on Twitter or in e-mail for more details.



A4 Specs

Wikipedia now has a page on the Apple A4 SOC (system on a chip) with some specifics. It appears that it's a ARM-A9 multi-core chip combined with a PowerVR VXD. The article seems to suggest that OpenGL ES 3.x and OpenCL are both possibilities with this chipset.

No matter how you slice it, it's pretty fast for a mobile device and this bodes well for the next generation of iPhones if they can scale the A4 to the iPhone.



GizModo Also Knows About Not Sucking…

Since I linked to them when they denigrated the iPad, it's only fair if I also link to them when they do the opposite, so here it is. Good article.



Wednesday, January 27, 2010

Same Ol' Same Ol'…

There seems to be a pattern that's formed over the last decade. Apple releases a product. It's different. It doesn't follow the rules. It defies a lot of widely accepted assumptions. Then, lots of people all over the Internet, both so-called pundits and everyday schmoes pronounce that it's a horrible device that nobody in their right mind would ever want. It doesn't support Ogg! It has no floppy drive! It has no multi-tasking! Product X already does this and it's cheaper!. Etc., etc. ad nauseum.

And then the product goes on to be a huge success, expanding the perception of who uses that type of device greatly. The iMac, the iPod, the iPhone.

So, here we go again with the iPad.

Here's the thing: It's not a general-purpose computer. It's not a Mac. They did this really subtle thing to tell you that: they didn't put "Mac" in the name anywhere. If it had been the MacPad1, then maybe some of these complaints would be perfectly valid. Maybe expecting it to act a certain way would be appropriate.

But it's not a Mac. It's a new consumer device. It's targeted at people who do e-mail, surf the web a little, play a few games, watch some movies, and listen to music. It's not a replacement for a computer if you do more than that on your computer. But most people don't. We geeks are the minority on that point, and for many people, a regular computer is both overkill and frustrating. The iPad is not a tablet computer in the sense that Windows tablets are. But what it is, is all that the vast majority of people will ever want out of a computer, and it fits in a briefcase, purse, or backpack and weighs less than two pounds.

Don't fault it for not being what it's not supposed to be and don't assume that in the handful of hours you've had since you first learned about it, that you've put more thought into this product than the engineers and designers at Apple. You haven't. I haven't either. But I see what they're doing, and it's brilliant.

Now, I very much understand the fear that we're on a slow march towards a completely enclosed platform, and that would be a bad destination. But, since Apple sold well over 3 million Macs last quarter, continues to contribute to open source projects like WebKit and Darwin, and is continuing to innovate on the Mac with things like GCD and OpenCL, I don't think that's really where we're headed. I think that Apple has realized that we all have different computing needs, and is trying to provide the best computing experience relative to our needs across the spectrum. I don't ever see the iPhone OS running on an eight-processor tower with two large screens. Just because some of Apple's products are locked down doesn't mean that the only possible destination is a future where all Apple products are. I think we're going to a place where both professionals and consumers can get what they want out of computing devices. If we go anywhere else, people will start abandoning the platform.

But as long as there are computers running Mac OS X for those of us who need the power and flexibility, this trajectory is actually a good thing2. For the bulk of consumers, ease of use trumps power or flexibility. If it doesn't for you, don't buy an iPad, or at least, don't buy it as your primary computer.

And if you doubt the efficacy of targeting the consumer market like this, just think about the fact that there are now more iPhone OS devices in existence than there are Macs, even before the iPad ships.


1 Like many others, I'd love a true MacPad - a device similar to this in hardware that runs unfettered OS X. But that doesn't mean this isn't a good product or won't be a success
2 Think of how much less time you'd have to spend being tech support for friends and family if they were all on a locked down device like the iPad. Just sayin'



Multi-Resolution Device

I can't post SDK 3.2-specific code because it's under NDA, but I can post SDK 3.1 code. Now, let's say you wanted your application to be future-proofed to run properly in full-screen on some theoretical new device with more pixels than an iPhone. You might do something like this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIScreen *screen = [UIScreen mainScreen];
[window setFrame:[screen applicationFrame]];
return YES;
}

This assumes that your nib is set up using autosize attributes to properly scale. If you need separate nibs, that's another blog posting that'll come after the NDA is lifted.



GizModo Knows About Sucking, Apparently

Gizmodo was very quick to put up a page denigrating the iPad. Generally, for a consumer device, I would view disapproval from Gizmodo as a good omen for future success1, but wanted to address their statements anyway.

  1. Big, Ugly Bezel - Yeah, I thought it was too big when I first saw it, but ugly? It's black and shiny. What's ugly about it? Were you hoping for beige plastic, perhaps? Anyway, then I gave some thought as to how you would hold a device this size, and you know what? I bet Apple had a darn good reason for making it precisely the size they did. I'll bet on Apple's product design and testing against Gizmodo's kneejerk reaction any day
  2. No Multitasking This is a geeky need. Most people don't need it. I need it in my day-to-day computing so this would never be my primary computing platform, but as a media viewer, why would I need it? And most non-geeks concentrate on one app at a time, and as long as when they come back to the app, everything is where they left it, that's just as good as multitasking. Apple also has competitive reasons for doing this: they don't want you listening to Pandora, they want you listening to iTunes. You might see that as a negative, most people don't because most people use the default player anyway. On the Mac, most non-geeks use iTunes and on Windows, most non-geeks use Windows Media Player
  3. No Cameras Okay, I sorta agree with this one, though I'm not sure it's going to be that big of a deal in person for the people who it's targeted at. I don't see my mom having a burning desire to do videoconferencing, and for me, well, my phone has a camera already and I alway have that with me. My laptop also has a camera. How many friggin' cameras do I need? The iPad is kind of unwieldy to use as a camera, and videoconferencing on the road would likely be a huge battery suck.
  4. Touch Keyboard I'm not saying anything on this one until I've tried it, but if you need the keyboard all the time, buy a MacBook, this isn't the product you need. Move along, move along. Expecting every product to do everything is silly. I mean, my phone won't do the dishes, either, but I don't tell people it sucks.
  5. No HDMI/HD Video OutAgain, not sure most people will care. It's a tablet, not a media center. It's designed to be portable. It's NOT a DVD player. If you need a media player, buy an Apple TV or an inexpensive desktop computer.
  6. The Name iPadOkay, I've got nothing here. I don't like the name either.
  7. No Flash This is a bad thing? Please. This is the greatest thing to happen to the web since, well… ever. Lazy web devs who use Flash as a crutch won't be able to use it any more. Adobe has proven themselves incapable of delivering a good non-Windows version of the Flash runtime, so let's give content providers reason not to use the fucking thing. Or, I should say, another reason, because the iPhone has already started playing Flash's funeral dirge.
  8. Adapters, adapters, adapters Yeah, and if they had made it bigger to attach all those things that you'll only use rarely and at your desk, you'd be complaining that it was too heavy or too thick. Newsflash: it doesn't have a floppy drive either. Most people won't care. They'll use one or two adapters at their desk, and won't want them when they're elsewhere.
  9. It's Not Widescreen Yeah, well, I sorta had that same reaction, but with the black bezel, letter boxing isn't that big of a deal, and I doubt people would want this product if it was any longer. There's no real argument that they should have made it thinner to match 16:9 so, once again, I'm going to say that Apple had a reason for designing the product this way. I'm sure they tried many different form factors before settling on this one, so I'll go with their track record over Gizmodo's gut.




1- I'm not knocking the Gizmodo folks (too much). Just pointing out that they are geeks, like me, and are not necessarily the demographic that this device is targeted at.



Tablet Post Mortem

Okay, so even some of my "safe" predictions were wrong. This thing is basically an iPod Touch on steroids - bigger screen, faster processor. It does run the iPhone OS, which is still called the iPhone OS, and not all that much has changed. I can't talk specifics because the new SDK version that supports the iPad is under NDA, but if you know how to program the iPhone, you know how to program the iPad. They didn't even change the major version number (much to my joy, given that we've got the version number in our book titles).

My thoughts: at $499, it's amazing. Now a lot of developers are a little disappointed. Frankly, we're not the main intended audience. For a lot of people, this is the perfect computer. For my mom, who doesn't really like computers, for example. This combined with the keyboard dock is all the computer she wants or needs. Her music, her movies, she can e-mail, browse the web. That's all she does and would do. That's all she wants from a computer. The iPad does all that and doesn't confuse her with a desktop paradigm, she doesn't have to remember to empty the trash, or try to traverse the file system to find things.

A lot of people are wondering if there's a place for this. I have no doubt. At this price point, a lot of people will go with this over a frustrating $350 netbook. I'm actually considering going with the iPad for my kids when they need new computers. At this price, I can afford to. They would have iWork, Mail, Safari, and can play some games, and that's the vast majority of what they do.

Now, for me, I don't really have much of a use for this. I'll buy one, maybe even two, so I can develop for it and play with it. But it would never suffice for my main computer, and I'm not sure there's really a need for it since I have my iPhone. My interest in this device is professional, but I'm not a typical consumer.

The iPhone wasn't successful because geeks liked it. It was successful because everybody (well, almost) liked it. I think that's what's this is going to be. This is a non-geek's computer. This is the perfect computer for somebody who does e-mail, surfs the web, plays a few games.

I hope there's a way to get pictures and music onto this thing other than through the iTunes store or through a Mac. I hope this thing is capable of standing completely on its own. I'm not sure yet if it is, but to me that seems like the most important thing. If I ONLY have an iPad, can I get pictures off my digital camera and onto it? Does the keyboard dock have a USB port. I don't know, but if not, I think it will be its Achille's heel, because I think the biggest pool of potential users of this thing are non-geeks who will use this instead of a Mac, not in addition to one.
Apple has an accessory called the Camera Connection Kit that allows you to pull pictures directly into the iPad from a digital camera or SD card
And, fortunately, it doesn't have Flash.



The Fabled Tablet

I've been studiously not making predictions about the iPad/iSlate/iTablet or whatever the product being unveiled today turns out to be, but like most people, I'm assuming it will be a touch screen device that's larger than an iPhone. Beyond that, I have some guesses, but not even that many of those. I've learned with Apple that it's generally best to play wait-and-see.

I do think it will be based on the iPhone OS rather than Mac OS X, although it could be a completely new variant of OS X that borrows features of both Mac OS X and iPhone OS. The reasons I don't think it will use the Mac OS is that touch handling isn't an integral part of the responder chain. Cocoa Touch is also "cleaner" than Cocoa. By that, I mean there are less legacy libraries so less code that uses design patterns from a different era of programming. And, finally, for reasons that John Gruber talked about on Monday, because Apple has control over the entire OS with the iPhone OS, and Apple likes control.
Breaking: I've heard a rumor that the next version of the iPhone OS will be renamed to something along the lines of iOS, and that the device will use it. The rumor is from a credible source, so I give it a pretty high likelihood, though wouldn't say it's 100%
Beyond that, I don't know. Will it use window like OS X, or will every application take over the whole screen like the iPhone OS? My guess is the former. Will it have background processing? I'm betting on some form, but not necessarily full, unfettered daemons like OS X. I'm betting it has standard IO ports such as USB-2 and Mini DisplayPort, and will use a more powerful chip than the iPhone. And those are all safe bets, I know, but I don't profess to be a prophet.

I've seen a number of "pundits" making dire predictions about the tablet being unpopular in business and have even read articles discussing how to keep the things off their networks. FUD. Probably written by bloggers who take money in some form or another from Microsoft. If not, then they're written by people with a complete lack of vision.

I also read this article. It's focused on one industry: Healthcare. But many of the points it makes are relatively universal. I spent many years working in so-called "Enterprise Software" for my day job, writing and implementing complex software in large corporate and government environments. From my experience, the vision in this article is spot on, just stated in terms that are too narrowly focused.

Think of all the specialty computing devices that get carried around on a daily basis. Think of the devices that the UPS drivers, FedEx Drivers, and Postal Carriers all have. Now think of all the jobs where a portable device COULD be used, but aren't. Think of the way the Apple Store employees use iPod Touches to check out customers. If today's announcement delivers on the unspoken promises, the possibilities are quite literally endless. A hotel concierge, for example, could be freed up to do his or her job anywhere in the hotel with a tablet. Maitre's D's could have their reservation information and access to their in-house beeper system under their arm, rather than be tied to a podium. School teachers could walk around their room and take attendance rather than having to sit at his or her desk marking in a gradebook or typing into a regular computer.

One prediction I feel comfortable making: The tablet, like the iPhone, will be a programmable device priced so that even consumers can afford it. Also, the touch screen will mean that its interface can adjust to whatever task needs to get done, not unlike the bridge controls on the U.S.S. Enterprise back in Star Trek:The Next Generation. That means it can be made to do most anything somebody needs it to do.

There are a lot of small and medium-sized businesses out there that can't afford to have a specialty device designed and programmed the way FedEx can, but certainly could afford a couple of consumer-priced tablets and the cost of developing a small, special-purpose piece of software, or to license a piece of niche software. There are a lot of jobs that involve walking around and where even a laptop computer isn't practical, but a touch-based tablet would be. The tablet and the iOS SDK (or whatever it ends up being called) could revolutionize and democratize custom software development by extending it into jobs where computers have been a viable option before.

I think the opportunities for experienced Cocoa and Cocoa Touch programmers are huge, whatever Apple releases today.



Monday, January 25, 2010

Flash and Fireball

Daring Fireball's John Gruber and I don't see eye-to-eye on everything, but we do when it comes to Adobe Flash.



360 iDev Conference, San Jose 2010

I am very sorry to report that I have had to back out of going to 360 iDev San Jose due to some personal commitments that have arisen. I'm very bummed about this. John and Tom put on a great conference (you should go), but I have a conflict in my personal life that I simply cannot get out of.

Hopefully I can make Denver in the fall. But, seriously, you should go.



Friday, January 22, 2010

Crime and Punishment

KRAPPS.com has a very disturbing article about Apple's action against the developer of the forChan app. The forChan application is simply an image scraper designed to work with image boards such as 4chan. While many of the pictures posted on these imageboards (which are all posted by users) are innocuous, many of these imageboards cannot be described as anything but the nasty, grimy underbelly of the Internet.

ForChan.app is simply a client to a particular type of web application that is widely used for non-porn images as well as porn. The app scrapes the imageboard HTML for image tags, then presents all of the images from one subboard as thumbnails, allowing the user to view them larger by tapping on a thumbnail. The app doesn't, itself, contain porn or do anything that clearly and undisputedly violates the SDK agreement.

After Apple realized that they let on a dedicated client tailor made for some of the nastiest places on the Internet, they quickly pulled the application. Had they stopped there, it would have been wrong, but not on any kind of epic scale. It would have been just another example of the risk of working in the Cathedral1. We all farm Apple's land, and we all know they have a certain amount of power over us. But, Apple didn't stop there, they then revoked the developer's certificate and removed all of his other applications from the App store. In one arbitrary, unappealable step, they completely removed one developer's source of income and made the product of many, many hours of work completely useless. The reason? Because he "deceived Apple about the intent of the application".

This is unfair in ways it's hard to describe.

What was the developer's deceit that was so bad it warranted completely destroying his livelihood? Well, he um… didn't default the application to show porn, which Apple has decided is the app's main purpose. Despite the fact that the very name of the application, and certainly the description submitted were very clear about what the application does (hell, the application is named after one of the seediest imageboards on the net). Because whomever reviewed the app wasn't savvy enough to pick up on the App's purpose the first time through, they've declared the developer to be dishonest.

I give Apple the benefit of the doubt whenever there is any to give. I have defended their actions a few times when they might not have deserved to be defended. But this is too far for even me. This kind of arbitrary and devastating action is disproportionate punishment and I can't see any justification or defense.

Apple should reinstate Charles Rodriguez's developer credentials and restore his other apps to the App Store immediately along with an apology. I doubt they will, but that's the right course of action for them to take.



1- This is a reference to Eric S. Raymond's classic comparison of commercial and open source software models The Cathedral and the Bazaar.



Thursday, January 21, 2010

Chapter 4 and the Tale of the NSFetchedResultsController

Okay, some people have been experiencing sporadic problems with the Chapter 4 application as described here. The solution I'd like to use would require being able to determine the number of pending, uncommitted section inserts and deletes that a table view has. Although I can get to this information, I can only do so by accessing a private instance variable of UITableView. Obviously, I don't want to give you all a solution that's going to get your application's rejected during the review process.

So, I went back to the drawing board. I don't like this solution as much since it requires us to duplicate work that the table view is already doing by keeping a shadow count of inserts and deletes, but it seems to work well and doesn't add too much complexity. I now have a pretty thorough test case for inserting and deleting rows from a table that uses an NSFetchedResultsController and this solution passes it, so fingers crossed.

The Solution


The first step is to add a @private NSUInteger instance variables to the controller class that manages the table and fetched results controller. This will keep a running count of the number of sections inserted and deleted during a batch of table updates.

In context of the Chapter 4 application, that means adding the following bold line of code to HeroListViewController.h:

#import <UIKit/UIKit.h>

#define kSelectedTabDefaultsKey @"Selected Tab"
enum {
kByName = 0,
kBySecretIdentity,
}
;
@class HeroEditController;
@interface HeroListViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UITabBarDelegate, UIAlertViewDelegate, NSFetchedResultsControllerDelegate>{

UITableView *tableView;
UITabBar *tabBar;
HeroEditController *detailController;

@private
NSFetchedResultsController *_fetchedResultsController;
NSUInteger sectionInsertCount;
}

@property (nonatomic, retain) IBOutlet UITableView *tableView;
@property (nonatomic, retain) IBOutlet UITabBar *tabBar;
@property (nonatomic, retain) IBOutlet HeroEditController *detailController;
@property (nonatomic, readonly) NSFetchedResultsController *fetchedResultsController;
- (void)addHero;
- (IBAction)toggleEdit;
@end



Now, we have to switch over to the implementation file, HeroListViewController.m and add a line of code to reset the insert count when we get notified by the fetched results controller that changes are coming. To do that, we add one line of code to the method controllerWillChangeContent:, like so:

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

Next, we have to increment this variable whenever we insert a section, and decrement it whenever we delete a section in controller:didChangeSection:atIndex:forChangeType:. We do that by adding the bold code below:

- (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];
sectionInsertCount++;
}


break;
case NSFetchedResultsChangeDelete:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1) )) {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
sectionInsertCount--;
}


break;
case NSFetchedResultsChangeMove:
break;
case NSFetchedResultsChangeUpdate:
break;
default:
break;
}

}

Finally, any time we do our consistency check in controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:, we have to take the pending inserts and deletes into account. Since we do the check more than once and insert new sections when the check fails, we also increment the variable if we do insert new rows. We do all that by adding the bold code in below to that method:

- (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 + sectionInsertCount != 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];
sectionInsertCount++;
}


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 + sectionInsertCount) {
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];
sectionInsertCount++;
}



[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;
}

}


I'll push this new code into the project archive as soon as possible and get it posted to apress.com and iphonedevbook.com, but here is the updated version of the Chapter 4 Xcode project in the meantime.

Don't worry if you don't understand everything that's going on in this code. This is nasty code designed to be completely generic so you don't have to worry about it at all. Hopefully this will be the end of our troubles with NSFetchedResultsController.



Coming Soon… One Week with Android

Don't worry, I have no intention of leaving the iPhone SDK as my main programming platform or the iPhone as my primary phone, but in the interest of being an informed fanboy, I've been using a Nexus One this week, and I've been porting some small apps to Android. I'll write up my observations and thoughts about both the phone and the SDK this weekend.



Wednesday, January 20, 2010

Another TableView / NSFetchedResultsController Gotcha

If you've followed this blog for any length of time, you know that I've been locking horns with NSFetchedResultsController and periodically releasing updated versions of the Navigation-Based Core Data Xcode Template to address the various problems, inconsistencies, and gotchas that I've uncovered during my fight.

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];