Wednesday, September 9, 2009

Bill Bumgarner on +initialize

This is a must read for anyone who overrides +initialize. I'll be honest, I either didn't know about this behavior or forgot it worked this way.

More on Core Data

Here is a correction and clarification on the Core Data problem reported here.

It appears that mergedModelFromBundles: does, in fact, work correctly with versioned managed object models. The problem is that Xcode doesn't clear out the old, non-versioned model when you version your data model, so you end up with both the versioned .momd and the original .mom file in your bundle and it attempts to load both, which is what causes the conflict, not the different versions inside the .momd.

Doing a Clean All should fix the problem, however I've tested this a bit, and sometimes you have to uninstall the app or reset the simulator to fix it, though I haven't isolated why yet.

Personally, I'm going to stick with the solution in the previous post for the time being. By specifying the versioned file explicitly, this can't be a problem either in testing or for your users. If you prefer to use the default template version of managedObjectModel, then make sure you do a Clean All after versioning your data model.

Thanks to Jack Nutting for pointing me in the right direction on this.

9/9/09 Event

Apple just finished their special event. They announced new, faster, and bigger iPod Touches, colorful iPod Nanos with a video camera, a new generation of the tiny Shuffle, and iTunes 9 with a bunch of nice new features, and iPhone OS 3.1 (I probably missed a few things there, but those are the main ones).

But the big news for developers is that there are now over 50 million iPhone OS devices in existence. Admittedly, some have been decommissioned, but any way you look at it, that's a lot of devices for a platform that's only been around a little over two years.

The breakdown, according to Steve is that they've sold more than 30 million iPhones and over 20 million iPod Touches.

To put that in perspective, it's greater than the installed based of all Mac OS X devices. Installed base is always an estimate, but the estimates I've seen put the Mac installed base at between 30 and 40 million devices.

Yes, the app review process can be a pain. The seemingly arbitrary rejections suck when they happen, and the expected low price point can be problematic. But, there really isn't a close second. There isn't a viable alternative. There's no single place to reach this many mobile users this easily, even in those situations where it's not necessarily that easy.

Tuesday, September 8, 2009

Table View Cells in Interface Builder - the Apple Way™

When Dave and I originally wrote Beginning iPhone Development, the SDK was very much in beta, and a lot of the documentation was incomplete or completely lacking. Given that, I think we did a pretty good job trying to stick with Apple's "best practices" and at guessing at which techniques would later evolve into best practices. In a surprising number of places, we do it the Apple Way™, even though we weren't 100% sure what the Apple Way™ would be when we were writing the book.

We did well, but we didn't bat 1.000.

One place where we missed was in loading table view cells created in Interface Builder.

In the first edition of the book, we hardcoded the index of the cell. That was bad, because when 2.1 shipped, the structure of the array returned by NSBundle changed and our code broke. Whoops!

In the second edition, we changed it so that the code no longer uses a hard-coded index, but instead iterates over the returned array looking for an object of the right class (UITableViewCell). That was a much better approach and works fine.

Somebody at Apple thought of a better way. It's effing brilliant. So brilliant, in fact, that when I first encountered the documentation for it, it took me a while to figure out just what they were doing. Here's the example code from Apple's documentation:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = @"MyIdentifier";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:@"TVCell" owner:self options:nil];
cell = tvCell;
self.tvCell = nil;
}

UILabel *label;
label = (UILabel *)[cell viewWithTag:1];
label.text = [NSString stringWithFormat:@"%d", indexPath.row];

label = (UILabel *)[cell viewWithTag:2];
label.text = [NSString stringWithFormat:@"%d", NUMBER_OF_ROWS - indexPath.row];

return cell;
}

This is what confused me at first:

    if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:@"TVCell" owner:self options:nil];
cell = tvCell;
self.tvCell = nil;
}

The method loadNibNamed:owner:options returns an NSArray with the contents of the nib, but this code completely ignores that array. It doesn't even capture it. Then, it goes on and blithely assigns some instance variable to another instance variable, and then sets the first instance variable to nil.

Apple's documentation isn't perfect, but usually, it's pretty damn good, so I was pretty sure this code was right, I just wasn't getting something. There was a piece of the puzzle missing.

So, I looked at tvCell, which was declared earlier, and saw that it was an IBOutlet.

“Curiouser and curiouser!” Cried Alice.

Then it dawned on me. The bundle loader automatically connects outlets made to File's Owner when it loads a nib file if the outlet is nil. Notice that when the nib is loaded, the code specifies self as the bundle's owner. So, since this controller class is the File's Owner, when we load the nib, the outlet will get connected to an object in the nib if the outlet with that name on File's Owner is connected to an object in the nib.

So, what's happening is, the cell is created in its own nib file. The File's Owner for that nib file is our table view controller where the code above resides. In the nib file, the tvCell outlet on File's Owner is connected to the custom cell that gets created.

So, after this line of code:
[[NSBundle mainBundle] loadNibNamed:@"TVCell" owner:self options:nil];

tvCellis automatically connected to the custom cell that was created by virtue of the outlet connection in the nib. There's no need to loop or worry about indices. The bundle loader just does it automatically. So, that custom cell is then assigned to cell which is the table view cell that this method returns to the table to display the contents of this row. After that, tvCell is set to nil so that the next time this method gets called, the bundle loader once again connects the outlet for us.

That's just brilliant. Kudos to whomever at Apple thought of it.

If you'd like to see it in action, here's an Xcode project that uses this technique.

Menlo

By request, the code samples on my blog will now appear in the Menlo font if that font is installed on your system.

Core Data Migration Problems?

More iPhone 3 Development is going to have a brief discussion of migrations. We're going to use automatic migrations between chapters when we add to or change the data model. We're not going to do be discussing the more complex manual migrations, as Apple covers that topic pretty well in Core Data Model Versioning and Data Migration Programming Guide.

Working on the book, I discovered that there is a real gotcha in using migrations with Core Data on the iPhone. So far as I can tell, the way that you need to change your project for migrations isn't documented anywhere. It may be in there somewhere, but it certainly doesn't jump out at you.

After you create the first new version of your data model, the first thing you have to do (if using automatic migrations) is enable the automatic migrations in your persistent store coordinator. This step actually is documented, and it involves just creating an NSDictionary and passing it in when you create your persistent store coordinator. The modifications to the accessor method that was created for you automatically in your application delegate are in bold below:

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}


NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"Foo.sqlite"]];

NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil
]
;

NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {

NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}


return persistentStoreCoordinator;
}

From reading the documentation, it looks like that's all you have to do. Since you're done, you dutifully fire up your application and get…
Can't merge models with two different entities named 'Foo'
Well, Frack.

Here's what's going on. The .xcdatamodel classes don't get copied into your application bundle as-is. Instead, each one gets compiled into a new file of type .mom (managed object model). When you add a new version to your project, the current version gets compiled into a .mom file in your application's bundle in the Resources folder. It doesn't stop there, thought. It also creates a folder (technically, a bundle) in Resources named after your data model but with a .momd extension. Inside the .momd bundle, it puts a compiled .mom file for each of the other versions of your data model. This is understandable. It needs this information to do the automatic migration.

Here's the problem, though. The default managedObjectModel accessor method that gets created on your application delegate creates a managed object model using a factory method called mergedModelFromBundles:. This method iterates through all the files in your application's bundle looking for all .mom. It iterates the directory structure, even going down into folders and bundles. Here's what the method looks like as created for you:

- (NSManagedObjectModel *)managedObjectModel {

if (managedObjectModel != nil) {
return managedObjectModel;
}


managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
return managedObjectModel;
}

The managedObjectModel hmethod is completely unaware of the fact that a .momd folder contains older versions of the same data model, and so it attempts to merge every version of your data model into a single merged model. Since different versions of the same data model typically have the same entities, the merge fails and you get an error message similar to the one above because entities must be unique. You can't have too "foo" entities in a single data model.

Here's the step that seems to have been missed in the documentation1. If you want to use migrations, you have to manually create your data model from the .momd bundles. Yep, can create a data model by providing a path to the .momd bundle instead of to a .mom file, and that's what you should be doing if you're going to use versioning. Here's what the new version might look like if you've got a single data model:

- (NSManagedObjectModel *)managedObjectModel {

if (managedObjectModel != nil) {
return managedObjectModel;
}


NSString *path = [[NSBundle mainBundle] pathForResource:@"Foo" ofType:@"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];

return managedObjectModel;
}

NSManagedObjectModel requires a URL to manually specify the location of a file, so we first get the path from NSBundle, then use it to create a file URL, then use that to load the data model file.

With this new version in place, automatic migrations should work, and you should be set up to do manual migrations based on the instructions in Apple's documentation.


1 - In Apple's defense, the documentation does say you can do this, it just doesn't make it clear that you need to.

Joy of Tech

I don't know if anybody noticed yesterday's Joy of Tech, but if you look closely in panels two and three, you can see the cover of Beginning iPhone 3 Development. In the lower-left right corner of both frames, there is clearly a black book with a grapefruit.

I think this counts as two of our fifteen minutes of fame.

Sunday, September 6, 2009

Backwards Compatible OpenGL ES 2.0

Here is an ambitious open source project intending to make OpenGL ES 2 code on the iPhone backward compatible. I haven't had time to check this out yet, but I plan to when I get some free time. It seems like this could be a godsend for developers facing the decision to use ES 1.1 or 2.0.

All the Little Lies

On my latest AT&T rant, I had a few people remark, basically, "who cares about MMS?".

The fact is, I don't. I have no plans whatsoever to use MMS. I'd like tethering, but I'm not willing to pay an additional charge to use it; I'm already paying for "unlimited data transfer" on my plan. I'm not exactly sure about the math involved, but I'm pretty certain that tethering won't cause me to use more than "unlimited" and it's not really AT&T's business just how I use that transferred data. Even when they do get it working, unless AT&T changes their policy with regards to tethering, I'll just forego it.

But, my earlier rant wasn't really about MMS. It wasn't about AT&T missing a deadline by two days. Hell, I've missed deadlines by two days before; I'm behind schedule on More iPhone 3 Development (which is why I'm sitting at my desk on a Sunday morning). At its core, my rant was s about something bigger.

It's about AT&T's approach to interacting with their customers. It's about the way they treat us and the complete lack of respect they show for us. AT&T has no qualms about flat out lying to their customers. They have a pattern of never taking any responsibility for problems their customers have, even ones that can't possibly be anybody else's fault but theirs. If you talk to customer service, it's like they all took a course in Defensive Talking as part of their employee training.

Here's just one little example. I logged on to AT&T to pay my iPhone bill this morning. This is the message we all get when we log on to pay (you've probably seen it):


You can click on the image for a bigger version if it's too small to read, but it says:
Your login is being verified. Depending on your Internet connection speed, it may take a few minutes for this process to complete.

And it does take a fair length of time to login - much longer than any web site that I use on a regular basis. But, that has nothing to do with my Internet speed, and the person who typed that message almost certainly knew that. I'm on a cable modem, and I paid this bill very early on a Sunday morning. Cable modems are shared bandwidth, so the less people online, the faster your service:



The transfer speed for pages, like AT&T's payment page that are mostly text, is meaningless when you're pulling down 12 megabits per second. Of the twenty seconds or so it took to log in, less than a second was due to my "Internet connection speed" Pinging that server resulted in a ~25ms round trip, which is 0.025 seconds. Worst case scenario, maybe a second or two was eaten up by transfer time and latency, or about a tenth of the time it took to log in.

AT&T obviously realized that logging in to their system would take longer than most users would expect it to take. Instead of fixing it, or just saying "hang on a second, we'll be right with you", they try to push the blame off of themselves and onto their customer and their customer's ISP. Believing, possibly rightly so, that most of their customers either wouldn't be savvy enough to realize that they were being lied to or just wouldn't care.

When it comes down to it, I don't hate AT&T because of their spotty coverage, failure to predict reasonable growth in 3G usage, or any of the other things that I've complained about here. Those are all just symptoms of a much bigger problem.

I hate AT&T because they seem to hate me (and you, too). At very least, they don't respect me, and they certainly don't take responsibility for their actions. We wouldn't accept this type of behavior from the real people in our lives. If I went to my publisher and told them I was behind on the book because of something my publisher did that was only tangentially related to my writing, they wouldn't believe me, and they'd be pissed off at me to boot.

Why don't we hold corporations like AT&T to that same standard we hold individual people? Why have we become so tolerant of abuse and disrespect from large corporations?

Saturday, September 5, 2009

Saving Tabs

I've been so ranty lately that I thought I should take a short break from writing More iPhone 3 Development to put up something technical. This is a relatively beginner-level tutorial, but it's one of those little features we take for granted but really hate when it's not there (at least I do).

I'm talking about saving the selected tab. When you start Apple's Clock application, for example, it always takes you to the tab you were on when you last used it. In most cases, this is a very good idea. There may be cases where a specific tab should have preference, but generally, you should bring the user back where they left off.

If you're using a tab bar controller, it's relatively simple to implement this. In viewDidLoad or applicationDidFinishLaunching:, you need to do something like this:

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSInteger whichTab = [defaults integerForKey:kSelectedTabDefaultsKey];
tabBarController.selectedIndex = whichTab;

Then, either when the selection changes, or when the application quits, you have to store off the tab in the preferences so it'll be there next time. I prefer doing it whenever the tab selection changes because then we've captured it, even if there's a crash or phone call, but doing it when the application finishes is fine for most purposes. Here's how you reverse the process:

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSInteger whichTab = tabBarController.selectedIndex;
[defaults setInteger:whichTab forKey:kSelectedTabDefaultsKey];

But what if you're just using a UITabBar and not a UITabBarController? That's a touch more difficult because tab bars don't work by indices. It's relatively easy to add the exact same functionality that's on the tab bar controller to the tab bar by using a category, however:

UITabBar-Index.h
#import <Foundation/Foundation.h>

@interface UITabBar(Index)
- (NSUInteger)selectedIndex;
- (void)setSelectedIndex:(NSUInteger)newIndex;
@end



UITabBar-Index.m
#import "UITabBar-Index.h"

@implementation UITabBar(Index)
- (NSUInteger)selectedIndex {
return [self.items indexOfObject:self.selectedItem];
}

- (void)setSelectedIndex:(NSUInteger)newIndex {
self.selectedItem = [self.items objectAtIndex:newIndex];
}

@end


Once you have that, you can import this category and use the exact same code from above on the tab bar rather than the tab bar controller.

Here's an example project. It includes a functioning tab bar application that saves the tab position, and also contains the category above.

Debugging Drawing

Apple has a nice little utility called QuartzDebug in /Developer/Applications/Graphics Tools, and one of the features of this program is that it will highlight all redrawing that gets done in every application. Every time a view is redrawn, it flashes. This is a great tool for determining if you're doing unnecessary drawing in your drawRect: method, or if you're somehow triggering unneeded redraws some other way.

I've had a lot of people ask me about doing this on the iPhone. I've been told by several people that they saw this functionality demonstrated at the iPhone tech talks, which had me stumped. I completely forgot that you can get very similar functionality by passing an argument called NSShowAllDrawing to your application on launch. This is mentioned in this excellent technote on debugging.

Michael Fey has a (somewhat holder) blog post, complete with video, that shows how to use NSShowAllDrawing. His example shows using it in a Cocoa application, but the process is exactly the same for using it in an iPhone application.

Update: A couple of people pointed out in the comments that this option is also available using Instruments. If you run with the Core Animation template, there's a checkbox labeled "Flash Updated Regions", which is checked in the screenshot below:



I haven't been able to get the Core Animation template to work for me on the Simulator, though. I'm not sure if that's by design, or a problem with my installation.

Friday, September 4, 2009

Apologism from AT&T

AT&T has posted a video on YouTube where their resident blogger, "Seth", claims to "take head on" our complaints as AT&T users.



The explanation sounds okay...

That is, if you don't actually let your brain function while "Seth" talks. The fact that AT&T conveniently left out of their explanation is that all that massive growth they've been seeing equates to massive growth in their monthly income. In other words, they had the resources, but they didn't plan well enough or spend enough of that massive gross income to actually provide the service all those people are paying $100 a month or more for. Their competitors in this country and all over the world somehow managed to do what AT&T didn't, and they try to make it sound like it's not AT&T's fault.

I can't speak for anyone else, but I don't find this video particularly satisfying or mollifying. In fact, to me, it looks like apologism of the worst sort. Don't make lame, after-the-fact excuses. Tell me when it's going to be better and say you're sorry for fucking up. Say you're sorry for taking gobs of cash from all of us each month and for not providing even halfway decent service much of the time.

Own up or don't bother talking. Don't tell us that by sending you all this money every month, we caused this problem. That's over-the-top ridiculous.

I don't put much faith in rumors, but I really do hope the ones about AT&T's exclusivity ending soon are true, because I'm about fed up with AT&T.

Sigh

Microsoft's marketing brain trust continues to look like a 40 year old with a combover trying to look cool at a high school dance.

C'mon Microsoft. You're not selling tupperware or cosmetics. Most sane people aren't going to celebrate a software release that they weren't directly involved in by inviting friends over and playing pin-the-tail-on-the-donkey. Even if you do manage to bribe some people with a free copy of Windows 7 and a chance at fabulous cash and prizes… well, the parties aren't going to look anything like this:


Sheesh. How can a company with so much money and so many smart people working there keep coming up with consistently moronic ideas?

That picture and the advertisement it's part of is a beautiful demonstration of what Jeff Atwood recently blogged about.

Thursday, September 3, 2009

OpenGL Workshop

Okay, listen up. Anyone who's planning on attending 360|iDev and who is also interested in learning OpenGL from a really, really great teacher, I've got some important news for you.

And no, the great teacher is not me.

But it is somebody I go to when I'm confused about OpenGL. It's a person who knows as much about computer graphics and game development as anyone I've ever met. The teacher is Noel Llopis, developer of (among other things) Flower Garden for the iPhone. Noel is an author, speaker, and game developer who is very active in the game industry, and he's teaching a threetwo day OpenGL ES class immediately before 360|iDev at the same venue. Hotel rooms at the conference price are available for the workshop, as well.

If you're interested in going, there's only one more day on the early bird rate, so you probably want to register ASAP.

This is the best resource currently available for learning OpenGL ES for the iPhone and spaces are limited.

Wednesday, September 2, 2009

A Camel Case Category

I recently needed to convert a Core Data attribute name, which uses camel case, so they could be displayed as capitalized words for use in a label. This would give me the ability to label rows in a Core Data backed detail editing view without having to manually specify the labels.

I decided not to use this option because I would have lost the ability to localize my application since my attribute names are always and only in English. But, I thought I'd throw this category up for anyone who might need it. This functionality could come in useful, for example, when building a Core Data developer utility.

NSString-CamelCase.h
#import <Foundation/Foundation.h>


@interface NSString(CamelCase)
-(NSString *)stringByConvertingCamelCaseToCapitalizedWords;
@end


NSString-CamelCase.h
#import "NSString-CamelCase.h"

@implementation NSString(CamelCase)
-(NSString *)stringByConvertingCamelCaseToCapitalizedWords {

NSMutableString *ret = [NSMutableString string];

for (NSUInteger i = 0; i < [self length]; i++) {
unichar oneChar = [self characterAtIndex:i];
if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:oneChar])
[ret appendFormat:@" %C", oneChar];
else
[ret appendFormat:@"%C", oneChar];
}

return [[ret capitalizedString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}

@end

360|iDev Denver

Unfortunately, I'm not planning on attending any more conferences this year. I really need to buckle down and get More iPhone Development done, and then I need to turn my attention to making money. As a result, I've turned down inquiries from both the 360 iDev and the Voices that Matter conferences this fall.

I'm really regretting the fact that I won't be able to attend the 360 iDev Denver conference, however. The inaugural conference in San Jose back in March was a great little conference. The upcoming one at the end of this month in Denver looks like it's going to be much bigger and at least as good.

In addition to speaking at the last 360|iDev conference, I had a secondary, kinda secret duty, which was to find potential iPhone authors for Apress so they could expand their catalog of iPhone offerings. Boy, was my trip successful in that regard. I handed off a list of names to Apress, and nearly every single one of them has contributed to an Apress title, writing either a chapter, or an entire book.

The caliber of people who attend 360 iDev is top-notch, both in terms of technical skill and just in terms of being nice, fun people. John and Tom, the organizers are great guys who work really hard to make the conference both valuable and fun. If I didn't have this book hanging over my head, I would certainly be going.

If you haven't signed up yet, you really should consider it. It's quite reasonably priced as far as conferences go, and you will meet a lot of people who for doing great things on our platform. You'll also learn a lot. It's a great place to make friends, find help, and just have a great time.

360|iDev Denver runs September 27 through September 30, 2009

Tuesday, September 1, 2009

Apple is Looking for a new Frameworks Evangelist

Apple is currently looking for a new Frameworks Evanglist. Honestly, this looks like the best job in the world, even if Matt Drance is going to be a really tough act to follow.

via Michael Jurewitz

Double the Fun - iPhone SDK Workshops in NYC and Boston


I had so much fun at the last workshop, I've agreed to do two more workshops. I'll be teaching another three-day workshop in New York City from October 2 to October 4 and in Boston from October 6 to October 8. As before, Steve Kochan will be teaching his excellent Objective-C workshop immediately prior to mine, and you can take either class or both as suits your current level of experience.

Me Blathering

Jonathan Sarno, who runs the iPhone Boot Camp Workshops took video during the Meetup I attended in NYC. We discussed a range of topics, including where things were headed with the iPhone, and how to monetize your games. I was expected to look like an expert, but I'm actually not an expert on either topic, but it was fun engaging in conjecture. Steve Kochan was also on the panel as well, but he's not shown in these two clips.



Monday, August 31, 2009

Sad Tales from the Android App Store

Makes you think twice before abandoning the iPhone, even with the hassles and review process problems, doesn't it?

Click-to-Flash

Just thought this was an apropos time to mention Wolf Rentzsch's wonderful Click-to-Flash, which disables the Flash plug-in from working in your browser unless you specifically authorize it. It's a wonderful tool that any Mac user should have installed (except you Flash developers, of course).

Flash Post Mortem

Okay, this is going to be my last word on Flash for a while. As fun as this has been, beyond this, I think that things will begin to get counterproductive and I have a lot of stuff I have to get done in the next few weeks.

I do appreciate everyone who took time to point out their perceived flaws in my argument, and to everyone who provided links and information about the Flash Platform with regards to the mobile web. Nothing I saw changed my mind about the future of Flash as a web development tool, but I feel better informed about Adobe's desperate attempts to hold onto the position they've carved out for themselves on the pre-mobile web.

Even if my predictions are completely wrong and Flash manages to survive as a dominant web development tool, it won't change my conviction that it's simply the wrong paradigm for the vast, vast majority of web development tasks. It's a fine tool for interactive presentations, kiosks, and limited cross-platform development. But for the web? It couldn't be a worse tool. Sorry. I know many of you disagree with me on this, but I've used a lot of web development tools and visited a lot of websites over the years on multiple platforms and Flash is just a back-asswards approach to web development. If Adobe changes the fundamental architecture of the platform, then I'm open to re-assessing Flash's worth, but until then, any enhancements or new features are nothing more than turd polishing. The shiniest, most jewel-encrusted turd, is still a turd.

But I'm completely unconvinced that Flash is going to survive the growth of the mobile web, at least as the dominant player it has been. Here's the thing that's missing. Adobe has created a consortium to get Flash on to "billions" of mobile devices. Who's not on that list right now? Apple and RIM, makers of Blackberry. When you look at the mobile web, either by units or packets transferred, those two have the lion's share of the market. Having a consortium to promote something on the mobile web without those two participating would be like having a consortium to develop standards for the regular web without including Microsoft. If you can't get at least one of them, and probably both, Flash is a dead end for the mobile web. That's just the political reality. Even if you can get RIM, you've still got the single largest smart phone in terms of units, packets transferred and, certainly, mindshare not participating in your "consortium". So, are companies going to want to do their sites one way for the iPhone and another for everyone else? Or are they going to start veering towards the open standards that will work on all devices? I think the slow move toward standards whenever feasible has already begun and eventually Flash will be left to handle the small number of niche jobs that can't be handled using Javascript/HTML/CSS.

On top of that, as long as Apple is in the position they're in with the iPhone not supporting Flash, other phone manufacturers are going to view mobile flash as "not mission critical", even if they join your consortium and let you put their logo on the consortium's web page. As long as that's true, these companies are going to view Flash as a potential marketing advantage and nothing more. They'll list it as a feature they have that the iPhone doesn't, but they're not going to expend huge resources making sure it's wonderful, because it's already been proven that people will buy smart phones that don't have Flash. These companies will do just enough to be able to put "Flash-enabled" in their ads and that's it. Again, it might not be right, but it is the political reality of how decisions are made at large companies. There's always too much to do and too few hours, and if you can't convince them all that Flash is mission critical, they're not going to pull resources away from other things to get it done.

Now, if Apple and RIM get onboard with Flash, that completely changes the political landscape. It won't change my mind about Flash being a poor choice for most types of web development, but it certainly will change its future viability substantially.

Nested Arrays

I'm staying the heck away from talking about Flash for a while.

In my quest for a good solution for handling table-based detail editing panes, I've been experimenting with using nested arrays to drive the table structure. A nested array is nothing more than an array of arrays (and I'm talking about NSArray instances here). The main or outer array contains one instance of NSArray for each section, and each subarray contains one object for each row in the section it represents. It takes multiple nested arrays to hold the structure of a table, with one nested array holding the labels, another holding the keys, and another holding the class of the controller class that can be used to edit that item. They're paired nested arrays, I guess.

This solution isn't quite as turnkey as the property-list driven solution I was working on earlier, but it's conceptually a lot easier to explain, and it doesn't squirrel away all the code I need to demonstrate into complex, generic classes. It's a hell of a lot better than having large nested switch statements in your controller class.

The entire process I'm developing will be shown in More iPhone 3 Development, but here's a category to make it easier to pull information out of a nested array in case you want to experiment on your own. This category adds a method to NSArray that lets you retrieve the right object from the nested array for a given NSIndexPath. Note that this category, like the table views it was created to support, supports only simple index paths that store a row and a section.

NSArray-NestedArray.h
//
// NSArray-NestedArrays.h
#import <Foundation/Foundation.h>


@interface NSArray(NestedArrays)
/*!
This method will return an object contained with an array
contained within this array. It is intended to allow
single-step retrieval of objects in the nested array
using an index path
*/

- (id)nestedObjectAtIndexPath:(NSIndexPath *)indexPath;
@end



NSArray-NestedArray.m
//
// NSArray-NestedArrays.m
#import "NSArray-NestedArrays.h"

@implementation NSArray(NestedArrays)
- (id)nestedObjectAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
NSUInteger section = [indexPath section];
NSArray *subArray = [self objectAtIndex:section];

if (![subArray isKindOfClass:[NSArray class]])
return nil;

if (row >= [subArray count])
return nil;

return [subArray objectAtIndex:row];
}

@end

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