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

Indie Relief

In the opinion piece I wrote yesterday, I stated that I didn't want to believe the Mac software market was dying. After thinking it through, I really don't think it is, but today I got yet another reminder of why I don't want it to be true: Indie Mac developers are just great people. That was one of the things that attracted me to Mac development back in the days when Mac development really was a dead-end as a career path.

Case in point: Indie Relief. Over 150 Mac and iPhone developers have banded together to offer all proceeds of the sales from their application to Haiti to help with the relief efforts there. Now, if you know about the economics of indie software development, you should realize that this is a pretty big deal. Few indie developers are living the high life, and many scrape by some months. Yet, all the developers listed on the Indie Relief page are donating every last cent of their income from their application for a period of time. We're not talking about donating $10 or $100 dollars, we're talking about basically handing over paychecks.

Take a look over the list, and if there's some software you've been thinking about buying, now is the perfect time to do it.

Tuesday, January 19, 2010

Greatly Exaggerated

Several people today tweeted a link to this blog post from John Casasanta of Tap Tap Tap about the death of Mac software. It's an interesting, post, and I'm having trouble deciding if I agree with it or not. I don't want to agree, that's for sure, but there are many valid points made.

My gut reaction, though, from which the title of this post is derived, is to paraphrase Mark Twain by saying the rumors of the Mac Software industry's death have been greatly exaggerated.

One of the assertions in John's post is that iPhone developers don't want to go back and develop for the Mac because the iPhone SDK is "shiny" while Cocoa is "old and crufty". I can't speak for any iPhone developers but me, but I really would like to spend more time with Cocoa. In the nearly two years since I jumped on board the iPhone ship, a lot of really cool things have happened to Cocoa, many of which aren't available to us on the iPhone yet. Blocks, GCD, and OpenCL mean that there are huge opportunities for new Mac applications, and mature garbage collection and instance variable synthesis mean even shorter development times. Heck, there are huge opportunities just to compete with and replace existing consumer applications, never mind for writing new applications. Can you imagine a Photoshop competitor that fully leveraged these new technologies1? Larger companies like Adobe with huge Carbon-based codelines have quite a challenge ahead of them getting their older applications to be 64-bit clean and running on Cocoa, which is a requirement for leveraging much of the cool new stuff. The large corporate software powerhouses are floundering in terms of modernizing their mainstay apps. To say there's not an opportunity there seems wrong to me. It may not be as easy or convenient of an opportunity as represented by the App Store, but there's definitely opportunity.

The Mac's market share is also higher than it's been at any time in at least ten or fifteen years and it seems to be trending up. In terms of actual installed base size, there are more people using Macs than ever in history. Even among people who don't use or like the Mac, the realization that it's not a "toy" operating system is slowly dawning on even the most ignorant of Apple haters. Well, okay, maybe not the most ignorant, but certainly everyone else.

More people are using Mac, so it's hard to imagine how the Mac Software market can be dwindling. If it is, it's likely a failure to take advantage of the opportunities that do exist. Perhaps we're all blinded by the bright, shiny App Store. Maybe stuff's not selling because there's not enough being written or marketed. Maybe we're all still buying into the gold rush stories subconsciously.

There's no doubt that the App Store is a rousing success and that it makes it far easier to reach customers, but hardly every iPhone developer is making a great living at this. TapTapTap is one of the great success stories, and the view from that perspective is very different from the the perspective of developers I've talked to who haven't recouped even enough to have made minimum wage for their time investment in their application. More than one iPhone developer are are looking for greener pastures, though many are having trouble finding one.

I do agree with John on many of the points in his post, however. I agree that it would be great if Apple opened up the App Store to Mac applications, but also agree that it seems unlikely that Apple will do it because they wouldn't have the same level of control. I also sincerely hope that John and I are both wrong on that. I find it odd that I can go into iTunes and buy movies, music, iPhone apps, and even donate to the Red Cross, but I can't buy Mac apps there. I can't even buy Apple's own Mac apps there like iWork and iLife. Last year, I ordered the latest version of iWork, both a single license for my business and a five-license family pack for home. I was able to just buy a serial number for the individual license, but for the family pack, I had to have a box shipped across the country to me. There's something wrong with that picture. I should have been able to just go into iTMS, specify the licenses I needed, then have the software download to my machine automatically. By now, we should have just as seamless and smooth of a buying experience for Mac applications as we do for movies, music, television shows, and mobile apps.

Even without a Mac App Store, though, opportunity is there in the Mac software world. In some ways, the opportunities are better than they've ever been because the potential audience is larger than ever and many of the people who are qualified to create quality Cocoa apps are myopically focused on the iPhone right now. Yes, there's more work involved with Mac apps. You'll have to find a distribution path. You'll have to advertise. You'll have to arrange a payment mechanism. But there are so many targets begging for a good competitor right now, and so many new as-yet uncreated markets that can now exist because of the amount of processing power we can easily leverage in Cocoa. There are many big, slow, corporate-owned, Carbon-based crappy-ass apps that keep making money because they have tons of cash to advertise and because there isn't a viable alternative or, at least, people aren't aware that there is a viable alternative.

We all started on a level playing field in the iPhone nearly two years ago. In fact, it wasn't even level at the start; smaller companies and individuals had the advantage of agility. Hell, a large corporation like Adobe or EA can't decide to enter a new market in the time that some of the earliest iPhone applications were designed, developed, and shipped. Heck, a large corporation often can't even decide who should decide to enter a new market in the time that many iPhone apps were written. TapTapTap was smart enough and capable enough to take advantage of that opportunity, but people entering the iPhone market today have to compete with the big names and the established small names.

Even though the distribution situation is considerably better on the iPhone than on the Mac, the overall competitive landscape really isn't all that different when you look at the market as a whole. How many of the top-ten grossing games right now are titles from big-name companies? Usually, it seems to run between seven and ten of the top ten are big-name titles. On the other hand, what percentage of successful Mac titles are produced by independents? I have to believe it runs at least 10-30%, and I would guess it runs higher.

I don't see the markets as being nearly as different as John does for somebody starting from ground zero today. There are differences, certainly, but there's plenty of room for success — and failure — in both markets.



1- Actually, I can. A couple of years ago, I abandoned Photoshop for Acorn, which is a native Cocoa image editor that rocks. There are a few features that some design professionals might need that it doesn't have (e.g. CMYK support), but what it does, it does so much faster than Photoshop CS4 that it's not even a close race, and yet it costs a fraction of what Photoshop costs. And think about this: Acorn is mostly written by one person. Compare that with the names in Photoshop's dialog box.

Monday, January 18, 2010

January 27th is On

Media Invites for the special January 27th event at the Yerba Buena Gardens are now out and it's official. It's still not official what's being announced, though most people are assuming it will be the fabled and long-awaited tablet.

Sunday, January 17, 2010

Wonderful E-mail

I've received a lot of e-mails from people who have read Beginning iPhone 3 Development. Anytime somebody thanks Dave and me for helping them to create an App, it feels pretty darn good, but a few e-mails stand out. I got one such e-mail, today, from Cameron Cohen, an eleven year old developer who already has an App in the App Store.

Now, in my mind, that's a pretty cool thing in and of itself. But it gets even cooler: Cameron is donating a part of the proceeds of his app to Mattel Children's Hospital UCLA's Child Life / Child Development program.

I don't normally blog about end-user applications — that's not the purpose of this blog — but I'm making an exception in this case. Check out Cameron's web site and also check out his first app, iSketch (iTunes Link).

Also, if there's anybody in the media reading this, I think Cameron's tale would make a great human interest story… I'm just sayin'.

Thursday, January 14, 2010

NSZombies in Instruments

If you've been an iPhone developer fro any length of time, you probably know about the eight wonder of the debugging world, NSZombie (if you don't, check out the last chapter of More iPhone 3 Development). What you may not know is that Instruments has recently added support for NSZombie. Mark Johnson has put together a little video showing how to use Instrument's NSZombie support right here.

Tuesday, January 12, 2010

Top 10 Pubs Near NSConference UK

Chris Walters assembled this extremely helpful list of the top 10 pubs near NSConference UK. What an awesome resource! The after hours socializing at conferences is at least as important as the day-time activities, so this is a list that is likely to be well-used.

Monday, January 11, 2010

NSConference 2010

Just wanted to remind people that NSConference Europe is coming up at the beginning of February in the U.K. and three weeks later in Atlanta, Georgia. I've agreed to give three presentations this year, one on Mac development, and two on iPhone development, though my Mac presentation actually talks mostly about Objective-C and foundation and is equally applicable to iPhone.

I'm really jazzed about both NSConferences, and am excited to be listed alongside some of these crazy good speakers. The speaker list this year includes Mike Lee, Wolf Rentzsch, Dave Dribin, Mat Gemmell, Drew McCormack, Aaron Hillegas, Marcus Zarra, and Andy Finnell, and I may have even missed some. If I were to include the previous conferences, the list would read like a who's who of Mac development.

If you're not already planning to go, you really should consider it. I've heard nothing but great things about NSConference.

I'll be arriving in the U.K. on Saturday, January 30, 2010, and this will be my first time there, so I'm looking forward to it. There's also a rumor going around that they have beer in the U.K. I'm just sayin'…

Thursday, January 7, 2010

CES Keynote

Part of my New Year's resolution was not to spend much time on opinion pieces and stick more to programming and technical topics. I can't let last night's disaster of a CES Keynote go without at least a brief comment, however. Watching Steve Ballmer on stage felt like watching QVC, only that comparison does a grave disservice to the QVC sales professionals who actually understand their audience, if not necessarily all the products they sell.


Things go wrong in live presentations, but they don't go as wrong as they did last night unless you really fucking try. It was a fail of epic proportions. From the constant technical problems, like tablets and mobile touch phones not detecting touches, to the use of Twilight to showcase the ability of the "slate" PCs to display eBooks (ZOMG, really? They can do that without a keyboard!?). You're presenting to the Consumer Electronic industry - a room full of geeks and press geeks, and you choose to showcase Twilight? Wow, talk about not knowing your audience.

Now, I know I bash Ballmer and Microsoft more than I probably should, but every year, Ballmer and Microsoft (with the exception of the Xbox division) look more and more buffoonish and less and less relevant. I've said it before, and I'll continue to say it until it happens: Microsoft needs a new leader. They need somebody who really groks technology (Ballmer clearly doesn't) and understands the way people use technology (ditto). The company has a huge number of incredibly talented and smart people. With the right leadership, Microsoft could absolutely knock our socks off and out-Apple Apple.

And that would be awesome for everybody. I can think of nothing better than having Microsoft create truly compelling products across their product lines. But they can't do it the way they are structured and with the present leadership. I'm not saying it would be easy for Microsoft to find the right leader, but almost anybody would be a step in the right direction from where they are now, and the right leader can make all the difference. Look at Apple ten years ago if you doubt that.

Last night's keynote was, quite literally, too painful for me to watch. After a while I just had to turn it off. I don't like watching people embarrass themselves. I really don't, and the press release was already available before the presentation started (oops!), so the salient points could be had without the agony.

Please, Microsoft, please… find a new leader. You owe it to yourselves and your customers to stop this painful decline at Ballmer's ham-fisted hands. Go watch Dr. Ed Catmull of Pixar explain how success masks problems and then find somebody who's got the brains, balls, and sense of showmanship to get you back into a leadership position.

Tuesday, January 5, 2010

Navigation-based Core Data Application Template Reposted

Based on some feedback to an earlier post, I was able to fix another bug in my version of Apple's Navigation-based Core Data Application Xcode template. It was an off-by-one error caused by using a >= when I should have used just a >.

New version of delegate methods follow:

- (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 (frcSectionCount < tableSectionCount && 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;
}

}

Three Billion and Counting

Today, Apple announced that there have been over three billion downloads from the App Store. Can you say "juggernaut"?

Thursday, December 31, 2009

Happy New Year! Closing Out 2009

Well, Happy New Year!

Thus ends (almost) my first full calendar year working exclusively with Apple technologies and also my first full calendar year as a published book author. I started working on Beginning iPhone Development in March of 2008, but because of the NDA and very beta-status of the early SDK releases, didn't start full-time writing on it until after WWDC 2008.

It's been a hell of a year. We finished the year on a positive note: More iPhone 3 Development shipped out a few days ago and some people have received it already. I've also heard that Beginning iPhone Development is fairly close to breaking 100,000 copies sold between the two editions. It's hard to say for sure if that has happened because of the way books are inventoried and sold, but our sell-through (books known to be in the hands of consumers) was over 75,000 several months ago. When you consider that Apress' expectation for our book when we contracted with them was that it would probably sell between 4,000 and 5,000 copies, that's really something. We were told before the book went to press that Apress would consider it huge success if it sold 10,000 copies.

I have to say that I love what I'm doing now much more than anything else I've ever done for a living, and infinitely more than what I was doing when I started writing the first book. I'm really glad I've been able to do this. I used to travel 48 or 50 weeks a year doing large-scale Enterprise software implementations. That was a realm where most of the challenges came not from the complexity of the algorithms (most of the work was pretty easy stuff), but the complexity of the social landscape and the politics of working in a large organization. As regular readers know, I'm not shy about giving my opinion even when it's unpopular, so you can imagine what I was like in highly-politically charged corporate environments. Yeah. I was miserable, even though I was good at it and it paid well. But the schedule was brutal. I'd leave my house like 2:00 or 3:00 am Monday morning or sometimes even Sunday night, and I'd get back late Friday night every week. I lived in hotel rooms and ate in hotel bars, and missed most of my kids' birthdays and other special events.

This is so much better. In many ways, this is the life. But, there's a downside to it as well, one that I've been aware of for a while but am only now coming fully to grips with: Even a "best selling" programming book doesn't really put the money on the table the way consulting does, and the blog doesn't generate any income worth mentioning. Some year-end financial calculations have made me realize that I've got some tough decisions to make about how I spend my time in 2010.

Books

At this point, I'm going to go out on a limb and say that I won't be writing any books in 2010. I really enjoy writing books, and have a couple knocking around in my head that I'd love to get out on paper, including a soup-to-nuts OpenGL ES book, but I really need to focus on projects that bring in money more effectively and more immediately (royalties take no less than six-months, and often longer to start rolling in).

Dave and I both have similar philosophies about writing books, and neither of us are willing to rush a book out just to capitalize on a trend or hot market. But that, combined with the way we work, means writing books is incredibly time-intensive for us and there's no way to avoid that without compromising what we believe. I'm not comfortable writing about something I don't fully understand, and it simply takes time to wrap my head around something well enough to explain it to others.

That being said, if someone came along and offered a huge advance for a book I wanted to write, I could be tempted. But given the reality of the tech book market, the chances of somebody offering the kind of money it would take to make it worthwhile are slim at best.

If 2010 goes well for me financially, though, I'd like to write another book in 2011.

The Blog

I have no intention of stopping work on my blog. I enjoy it, and some of you seem to as well. I will be more focused in 2010 and will mostly stick to technical subjects and covering things directly relevant to Mac and iPhone programmers. Writing lengthy opinion pieces is time-consuming and tends to provoke people in ways that I don't necessarily intend, even if I do get a lot of page hits from them. But I definitely do plan to continue posting installments of the OpenGL ES from the Ground Up as well as more virtual chapters for our existing books and code snippets that I write that might be useful to others.

The pace of new installments may not be as frequent as it was this past year, at least the part of last year when I wasn't actively working on a book, but the posts will definitely continue.

Teaching

I'm still thinking about whether I want to continue teaching workshops. It's decent money, and I rather enjoy it, but I'm not sure I want to get into doing regular travel again and I don't want the workshops to interfere with my ability to take on contracting work. I'll probably continue to teach, but probably not more than every other month or so. We'll see, though.

Conferences

I'm planning on doing a handful of conferences this year. I'm speaking at both NSConferences in 2010 and, of course, plan to attend WWDC assuming I can scrape together the money. I'd really, really hate to miss WWDC - it's my absolute favoritest week of the year - but unfortunately, it's not a sure thing at the moment. I've also been accepted as a speaker at 360|iDev San Jose.

Contracting

This is what I really need to focus on for the next year. It is my intention to stay working within the Mac and iPhone space if at all possible, so if you know of anybody looking to contract or subcontract an experienced iPhone or Mac developer, please do feel free to send them my way. Though I'm willing to travel occasionally, I am not interested in contracts that would require full-time travel or anything close to it. I'm also available for corporate training or reviewing code or application architecture. I'm a very experienced troubleshooter and have spent a lot of time investigating how to architect iPhone applications, so if you've got projects that are running into problems, I may be able to help get you back on track. As a general rule, I'm not currently looking for full-time employment, though there's a small handful of jobs (primarily at Apple) that I'd definitely consider if they opened up.

If you want to reach me about anything, you can send e-mail to jeff underscore lamarche at mac dot com.

So, on that note, I'm going to go enjoy my New Year's even and forget about anything to do with finances or the iPhone for a few hours. I wish you all a prosperous and enjoyable 2010, and I'll be back to posting in a few days.

MDN Community Awards

Today, somebody let me now that I've been nominated for the MDN Community Awards 2009. This came as quite a shock. I'm not sure who nominated me, but thank you. It's incredibly flattering to see my name listed alongside the names of so many people that I admire and respect.

I really don't envy the people at MDN who have to make the final decision on this. There are some really awesome people on that list, and I expect several more names to be on the list before all is said and done. It's not going to be an easy list to narrow down.

 
Design by Wordpress Theme | Bloggerized by Free Blogger Templates | coupon codes