Thursday, July 9, 2009

In Search of a Better Way: Editable Detail Views

Some of the ugliest iPhone code I've written to date - perhaps most of the ugly code I've written to date - has been in table view controllers acting as detail editing panes. Normally, when you use a table view you use it to present a list of data to the user from an array or fetch request, and for doing that, the table view architecture is beautiful. But, when you want to use it to present - and allow the user to edit - properties of a single object, things get a bit gnarlier.

Sure, you can use Interface Builder to build your editing panes. But, most people take their cues from Apple about how to do things, and detail editing views are usually implemented as tables. Take a look at the Contacts app, for example:



It uses table views. So does the Settings application. Let's face it, most of us are going to want to use the same approach.

But, it's hard to write good clean code to handle these types of detail editing views. Because the editing of any particularly property could be handled by a different controller class, it's very hard to write elegant code to implement these. You end up with a lot of similar yet not-easy-to-refactor code.

The most obvious way to write these controllers is to use enums to define your table view's sections, and the rows within each section, sort of like this:

typedef enum  
{
ProjectTableSectionNameSection,
ProjectTableSectionTasksAndExpensesSection,
ProjectTableSectionPrimaryContactSection,
ProjectTableSectionDateSection,
ProjectTableSectionBudgetSection,
ProjectTableSectionLocationSection,
ProjectTableSectionReportSection,
ProjectTableSectionCompleteButtonSection,
ProjectTableSectionDeleteButtonSection,

ProjectTableSectionNumberOfSections
}
ProjectTableSection;

typedef enum
{
ExpenseEditSectionName,
ExpenseEditSectionGeneral,
ExpenseEditSectionDate,
ExpenseEditSectionDeleteButton,

ExpenseEditSectionCount
}
ExpenseEditSection;
...

Then, in the implementation of your class, you write switch statements to handle the logic for each section and row combination. The advantage of this approach is that you can rearrange rows and sections just by changing the enumerations. Because each value in the enum is one higher than the one before, you just change the order of the constants, and the rows or sections change order in the actual table.

But…

If you have an object with many properties, divided up into multiple sections, you end up with nasty, hard-to-maintain code doing that. Sure, you don't have to rearrange or change large chunks of code to rearrange the visual appearance, but it's still hard to find the code you're looking for when you go to make changes or fix bugs and it's still mingling the view and controller parts of MVC together in an uncomfortable living situation. You're violating MVC, and the design of the table view controller is actually kind of encouraging you to do so.

To give you an example, here is an excerpt from one of the first (and utterly horrible) complex table-based editing views I wrote using this technique:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
switch ([indexPath section])
{
case ProjectTableSectionNameSection:
{
TextFieldEditingViewController *controller = [[TextFieldEditingViewController alloc] initWithStyle:UITableViewStyleGrouped];
controller.fieldNames = [NSArray arrayWithObject:NSLocalizedString(@"Project Name", @"Project Name")];
controller.fieldKeys = [NSArray arrayWithObject:@"name"];
controller.fieldValues = [NSArray arrayWithObject:project.name];
controller.shouldClearOnEditing = [project.name isEqualToString:NSLocalizedString(@"Untitled Project", @"Untitled Project - name given to new projects")];

controller.delegate = self;
[self.navigationController pushViewController:controller animated:YES];
[controller release];
break;
}

case ProjectTableSectionPrimaryContactSection:
{
if ([indexPath row] == [project.contacts count])
{
ABPeoplePickerNavigationController *peoplePickerNavigationController = [[ABPeoplePickerNavigationController alloc] init];
peoplePickerNavigationController.peoplePickerDelegate = self;

[self presentModalViewController:peoplePickerNavigationController animated:YES];
[peoplePickerNavigationController release];
}

else
{
ABPersonViewController *controller = [[ABPersonViewController alloc] init];
controller.personViewDelegate = self;
controller.allowsEditing = YES;
controller.addressBook = project.addressBook;
ABRecordRef theRef = [project recordRefForContactAtIndex:[indexPath row]];
controller.displayedPerson = theRef;
[self.navigationController pushViewController:controller animated:YES];
[controller release];

}

break;
}

case ProjectTableSectionReportSection:
{
ReportViewController *controller = [[ReportViewController alloc] initWithNibName:@"ReportView" bundle:nil];
controller.reportHTML = [project projectReportAsHTML];
controller.title = NSLocalizedString(@"Project Report", @"Project Report");
[self.navigationController pushViewController:controller animated:YES];
[controller release];
break;
}

case ProjectTableSectionDateSection:
{
dateBeingEdited = ([indexPath row] == 0) ? project.startDate : project.expectedCompletionDate;
DateViewController *controller = [[DateViewController alloc] init];
controller.delegate = self;
controller.date = dateBeingEdited;
controller.title = ([indexPath row] == 0) ? NSLocalizedString(@"Start Date", @"Start Date") : NSLocalizedString(@"Exp. Completion", @"Expected Completion Date (abbreviated to fit in title bar)");
[self.navigationController pushViewController:controller animated:YES];
[controller release];
break;
}

case ProjectTableSectionBudgetSection:
{
TextFieldEditingViewController *controller = [[TextFieldEditingViewController alloc] initWithStyle:UITableViewStyleGrouped];
controller.fieldNames = [NSArray arrayWithObject:NSLocalizedString(@"Budget", @"Project Budget")];
controller.fieldKeys = [NSArray arrayWithObject:@"budget"];
controller.fieldValues = [NSArray arrayWithObject:[project.budget stringValue]];
controller.shouldClearOnEditing = ([project.budget doubleValue] == 0.0);
[controller setKeyboardType:UIKeyboardTypeNumberPad forIndex:0];

controller.delegate = self;
[self.navigationController pushViewController:controller animated:YES];
[controller release];
break;
}

case ProjectTableSectionTasksAndExpensesSection:
{
switch ([indexPath row])
{
case 0: // Tasks
{
TaskCategoryViewController *controller = [[TaskCategoryViewController alloc] initWithNibName:@"TaskCategoryView" bundle:nil];
controller.project = project;
[self.navigationController pushViewController:controller animated:YES];
[controller release];
break;
}

case 1: // Expenses
{
ExpenseListViewController *controller = [[ExpenseListViewController alloc] initWithStyle:UITableViewStylePlain];
controller.project = project;
[self.navigationController pushViewController:controller animated:YES];
[controller release];
break;
}

case 2: // Change Requests
{
ChangeRequestCategoryViewController *controller = [[ChangeRequestCategoryViewController alloc] initWithNibName:@"ChangeRequestCategoryView" bundle:nil];
controller.project = project;
[self.navigationController pushViewController:controller animated:YES];
[controller release];
break;
}

case 3: // Budget Metrics
{
BudgetMetricsViewController *controller = [[BudgetMetricsViewController alloc] initWithStyle:UITableViewStyleGrouped];
controller.project = project;
[self.navigationController pushViewController:controller animated:YES];
[controller release];
break;
}

default:
break;
}

break;
}

case ProjectTableSectionLocationSection:
{
LocationViewController *controller = [[LocationViewController alloc] initWithStyle:UITableViewStyleGrouped];
controller.project = project;
[self.navigationController pushViewController:controller animated:YES];
[controller release];
break;
}

case ProjectTableSectionDeleteButtonSection:
break;
default:
break;
}

[tableView deselectRowAtIndexPath:indexPath animated:YES];
}


Pretty horrid, huh? Yeah. Don't do that, kids. And what's worse is, all that code was just to handle the user interaction from maybe ten rows of data divided into a handful of sections. And that's just one method in the controller class. There are longer ones, actually. It's ugly, unclean code any way you look at it.

So, ever since I wrote that monstrosity above, I have been on the lookout for ways to make the process of using table views to display and edit properties from a single data object using a table more manageable. Using generic controller classes like the ones I wrote a while back helped some, but not enough and doesn't fix the breakdown of the wall between the C and V of MVC. Unfortunately, I got busy, and I put the quest for a better solution on a back burner.

However, one of the applications we wrote for More iPhone 3 Development (on Core Data) uses one of these detail editing panes, and I really wanted to find something more elegant before I committed the code to print for the world to see. So, I dove back into this problem recently and came up with something I'm actually happy with, though it still needs some refinement. It's a a proof-of-concept stage now, but it's a very promising proof of concept.

Instead of writing a custom controller class for every detail editing view I need, I now can just create an instance of a generic controller class designed for these types of detail editing views. I pass the location of a property list file to the init method of that controller, and that property list defines the structure and appearance of the table-based detail editing view. It supports sections and different types of editors. You can have the user edit an attribute in a text field, or you can present a drop down list without writing any code, and you can add additional editors just by subclassing an existing class. Just assemble a property list using Xcode's built-in property list editor and pass that property list into the generic class.

Want to rearrange rows or sections? Just drag the corresponding entries in the property list to their new location. Want to add a new section or a new row within a section? Just add a new entry into the property list. Want to delete a row or section? Just delete the entry from the property list.

Here's an example from my proof-of-concept application. I've defined a property list like this:


Click for larger version


If you look at the property list, you see that there are two sections defined, and a total of three rows. The resultant application looks like this:



You see? Two sections, three rows, no code. I just instantiate the generic controller with the path to the property list:

    NSString *layoutPath = [[NSBundle mainBundle] pathForResource:@"HeroLayout" ofType:@"plist"];
ManagedObjectDetailEditor *controller = [[ManagedObjectDetailEditor alloc] initWithLayoutFile:layoutPath];
controller.managedObject = newManagedObject;
[self.navigationController pushViewController:controller animated:YES];
[controller release];

You might have noticed that the last dictionary in the property list (representing the sex of the superhero) has an additional key value called arguments. This gives the flexibility to pass additional data to the controller class, so you can do things like present a list of values that the user can choose from. In this case, we let them choose the sex from a list that includes Male and Female rather than making them type in free-form text.



This code is still in its infancy with many datatype editors left to be developed, but I think there's a lot of potential here for saving developers time. I'm even thinking about developing a little tool to let you visually design the table based on the Core Data data model - but that would be quite a ways down the line. Even without that and just crafting property lists rather than custom classes, there's a huge time savings.

The source code will be available as part of the More iPhone 3 Development source code archive, and one section of the book will include a tutorial on how to create a property list to define a detail editing view layout.

The Perils of Helping Others

Kind of a sad blog post by Brandon of iCodeBlog.com, a site with many iPhone coding tutorials. When you share information in tutorials the way Brandon does, you have to expect people to use that information. But you expect them to use that information to learn and improve their skills so they can go out there and do cool things. It's a gift of knowledge intended for people who want to learn. You don't expect them to take your work and publish it as their own, however.

This kind of asshattery is the work of a truly sad person.

You probably weren't tempted to buy iTennis, but if you were, please don't.

via KRapps

Wednesday, July 8, 2009

Core Plot

I just learned about an open source graphing library for the iPhone called Core Plot.



Looks like it's got a lot of potential, and I know a lot of people have asked about graphic libraries, so there you go.

via Michael Fey

More on Teaching

Looks like everything is shaping up for the August 14-16 iPhone Boot Camp workshop in New York City. I'll be teaching at the SL Conference center which is at 352 7th avenue. That's just a short walk from Penn Station for those of you who live upstate or in northern New Jersey.

If you're interested, you can sign up here.

The "What you will learn?" section on that page is fairly accurate, although I'm probably going to remove the discussion of SQLite, or at least reduce it in favor of Core Data. Now that we have Core Data, I think you'd be insane to use SQLite directly. There may be other minor tweaks to the syllabus, but I won't be drastically changing the basic flow of the class which is a tried and proven formula at this point.

If you are interested in taking the workshop and have not already signed up for Apple's iPhone SDK program, I would suggest you do so. It's not required for the class, but there may be a few exercises that you won't be able to test because they use hardware-specific features not supported in the simulator. I'll also be available after hours to help you with setting up your certificates and provisioning so you can run programs on your iPhone or iPod Touch, a process that can be a little daunting.

Note: If you are interested in taking this workshop, be aware that there is an early bird pricing option that will expire in seven days. Also, to ensure a good experience, the workshop does have limited capacity. As of this morning, four slots had already been taken.

Tuesday, July 7, 2009

An IPhone App Setback

Wil Shipley of Delicious Monster and a titan in the world of Cocoa programming recently tweeted some bad news. The free Delicious Library 2 companion iPhone App has been pulled from the App Store and won't be returning. Why, you ask?

Amazon's Afilliate Program API License Agreement, section 4(e) specifically prohibits the use of the API from programs that run on mobile devices.

I doubt Wil's the only one who has used the APIs from an iPhone App, so just thought I'd spread the word. If you're doing it, you're breaking the agreement and could be opening yourself up to liability.

That aside, I think it's pretty stupid and shortsighted of Amazon to make this particular and arbitrary restriction. It's also rather vague. What about using the API from web-apps accessed from an iPhone? What about accessing it from a Mac that's tethered through an iPhone? Amazon only stands to gain from traffic being directed their way. Sure, they could try and monopolize the mobile space, but most products that use the API don't directly compete with Amazon in any meaningful way.

Oh, Good, Here Comes the Justice Department

They did such a bang-up job with the Microsoft situation, I'm so glad to see that the Department of Justice is on the case! Now, if you read any of yesterday's grumpy rants, you know I'm not exactly a big fan of AT&T or of the ability of Federal agencies to improve, well, anything. Truth be told, I would LOVE to see the iPhone freed from AT&T.

But… this is just stupid. Check this quote out:

Among the areas the Justice Department could explore is whether wireless carriers are hurting smaller competitors by locking up popular phones through exclusive agreements with handset makers, according to the people.


They specifically call out the iPhone. Wow.

Yes, the iPhone is awesome. Yes, it's a competitive advantage. I mean, duh! Of course it makes it harder for smaller competitors to compete. That's the whole damn point! Have we forgotten what a monopoly even is? It's hard to imagine how it could be illegal to enter into a two- or five- year exclusive agreement with the manufacturer of a phone that accounts for 1.1% of the mobile phone market.

1.1%?

Playing Monopoly by the Department of Justices rules would be no fun, because you'd only need one property before you have monopoly. To heck with requiring players to have 100% of the properties with the same color. If you've got more than 0% of one color, you're good to go. Monopoly, baby. Start building houses! One house is at least 33.33% of a color, so you obviously have a monopoly there!

God, this would be such a waste of taxpayer money. It's is a sad attempt by the Executive branch to appear responsive to negative public sentiment about wireless carriers without any real legal basis and without any intent to actually fix or even really change anything (how'd that split-up of Microsoft work out?). This is using a duct-tape as a band-aid and sticking it nowhere near an actual wound.

Monday, July 6, 2009

Windows Mobile: World Domination is In Sight!

I broke the five-tweet rule today, which says that if it takes you more than five tweets to say something, it belongs in a blog posting. So, here's my blog posting.

Back in 2007, before the launch of the iPhone, Steve Balmer predicted that Apple had "no chance" of gaining more than 2% or 3% market share. He also predicted Windows Mobile getting to "60%, 70%, or 80%". At that point, Microsoft's two mobile platforms had a total of in the ballpark of a 20% market share and was on a gradual incline.

By February 2008, the iPhone had already overtaken Windows Mobile in terms of Smart Phone market share. That's not even a year later.

This past July, the iPhone overtook Nokia in terms of Smart Phone market share. Although Nokia is still the dominant manufacturer when you look at all phones, that's still impressive. Passing Nokia makes Apple the overall worldwide leader in smartphones! Even more impressive? Those numbers are from Q1, so they don't take into account the between one and two million iPhone 3Gs units that were sold last month. (Note - these numbers are not installed base, they are based on "mobile ad traffic". RIM may still have a larger installed base, but any way you cut it, these numbers are still impressive).

Being a technology pundit is notoriously hard, and I typically cut people slack when they attempt to predict the future, especially when they have an obvious bias such as Steve Balmer has. I mean, we have to expect people to believe in their own products. Of course.

But, here's the thing… Balmer has continuously made bad predictions that directly impact the products that his company offers. Under his watch, they've shipped horrible, horrible products, lost market share in most areas where they compete, failed to establish footholds in any new markets, and are basically slowly bleeding money out of their rectum. They are squandering an almost unsquanderable lead.

Why does this guy still get any respect? I mean, how often does he have to be completely wrong about his job before Microsoft's Board of Trustees gets rid of him and puts in somebody competent? How long can one ship be steered in the totally wrong direction before somebody on-board asks where the hell they're going and if they really should keep listening to their batshit-insane captain?

The HMS Microsoft needs a mutiny. Of course, their Fletcher Christian was probably an orange-badger who got let go once he finished coding to spec.

Note: My bad! I forgot about the Xbox. That shipped under Balmer's watch, makes money, and that team does some good work. Microsoft did, indeed, establish a foothold in the console space and gained considerable market share under Balmer's watch, so I overstated the case there a tad, but I think the overall point is still valid.

Core Data Navigation-Based Application

There's a problem with the Core Data Navigation-Based Application Template in 3.0. In the current 3.1 beta, the problem has been partially (but not fully) resolved (can't give details because of the NDA, sorry), but hopefully it will be fixed in the final version of 3.1.

If you use the template in 3.0 and run the sample application, the application will crash if you try to delete the last or the only row in the application.

The problem is here:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

if (editingStyle == UITableViewCellEditingStyleDelete) {
//[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

// Delete the managed object for the given index path
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
[context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];


// Save the context.
NSError *error;
if (![context save:&error]) {
// Update to handle the error appropriately.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
exit(-1); // Fail
}

[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade
]
;
}

}

What happens is that the object is deleted from the context here:

    [context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];

it causes the Fetched Results Controller to removes the object from its resultset and the corresponding table. So, when, a few lines later, when it attempts to delete the row from the table:

        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade
]
;

The row it's trying to delete is no longer there.

The solution to this, however, is non-obvious. In fact, I didn't figure this one out myself. In the 3.0 template, the following method is commented out:

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// In the simplest, most efficient, case, reload the table view.
[self.tableView reloadData];
}

The first thing you should do is uncomment this method. You want to be getting change notifications from your fetched results controller. However, if we leave it as-is, we'll start getting that same problem when we delete every row, instead of just when we delete the last or only row. To resolve this, you need to add one line of code to the uncommented method (it's in bold):

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// In the simplest, most efficient, case, reload the table view.
if (!self.tableView.editing)
[self.tableView reloadData];
}


Once you've made that change, you should be good to go. In fact, you might want to go and change the code in the project template by making the change to the file called RootViewController.m at the following location (assuming you've installed the dev tools in the default location:

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


Thanks to iPhone Developer Rod Brown of TheBarcodeProject for pointing me in the right direction on this issue!

iPhone Simulator Application Creator

This is pretty cool. A new program called iPhoneSimulatorExchange has been released. It is designed to let you create a one-click installer of your iphone app that runs in the installer. This lets you package up your compiled application so it can be run on the simulator of another machine that has the iPhone SDK and dev tools installed. A very neat, and useful idea. I'm looking forward to trying it out.

via Adrian Kosmaczewski.

Wireless Carriers the Great Train Robbers of this Millenia

I generally avoid chiming in with the whole "AT&T Sucks" chorus. Over the last fifteen years, I've had five different wireless carriers, and though AT&T isn't the best, they are far from the worst (which was MCI/WorldCom if you were wondering). They all suck, but some suck more than others and it's not like we have much in the way of options. We're a captive audience (especially iPhone users), so just like how you pay more for food at a baseball stadium or in a theme park, I actually expect to get gouged some for having an iPhone. I don't like it, but there's no sense pissing in the wind. If I hate them too much, I always have the option of choosing another phone. I don't hate them nearly that much.

But, I got my first bill since upgrading to the 3Gs, though, and found a few surprises and a few things I didn't understand in my bill, so I called AT&T for clarification.

Now, it appears that there were no mistakes in my bill, and the customer support representative was very nice and really was trying to help me out, but I kept finding the explanations about what things were kind of vague and just not clear to me. I didn't want to be a jerk to the lady on the phone, but I just wanted a clear explanation of the "Other Charges/Deductions" line items and kept asking her to lay them out in plain english rather than reading the intentionally vague explanation from her script.

There were, for example, these things called "Regulatory Cost Recovery Fee" and "Federal Universal Service Charge", which prompted the question from me "why aren't these under the section for surcharges and taxes.

The response was that these aren't mandated fees. These aren't required by any state or federal body at all. These represent money that stays in AT&T's coffers.

So, what are they?

In a nutshell? They are amounts that AT&T, in their sole discretion, is allowed to tack on to the bill to defray the cost of certain things. The "Regulatory Cost Recovery Fee", for example, is a fee they tack on to defray the expense of complying with state and federal regulations. The "Federal Universal Service Charge" is one they tack on to help with the cost of maintaining cell towers and other infrastructure.

Now, there's no documentation about exactly what costs are being defrayed and, as far as I can tell, not much accountability to the consumer on these. Sure, there are regulations about what they can include in these items, but there doesn't seem to be a requirement that they itemize or explain the charge, or how they arrived at the amount, to the consumer. Basically, these line items can be used to arbitrarily and randomly increase their income. If you multiply those charges (over $5 for me this month) by the millions of customers, it comes out to a fairly substantial amount of money.

This irks me in the same way that "fuel surcharge" by an airline or package delivery service or an "airport recovery fee" from a rental car does. Complying with federal regulations, fuel, and leasing space in an airport are all fucking operating expenses. They should be factored into the price of the product or service, not tacked on after you've agreed to the purchase based on the artificially low price that doesn't include random operating costs. When you hide the true cost in "recovery fees" and "surcharges", you make it impossible for the consumer to know what their actual real bill will be and impossible to intelligently compare competing products or services.

Wouldn't it be nice if you could do this with your paycheck? "Hey, Boss? Yeah, the head gasket on my car blew this month, you have to pay me extra this month as an 'Employee Transportation Recovery Cost'."

So, let me just break from tradition and chime in with one big "Fuck You, AT&T" to make myself feel better. The second the iPhone is available from another carrier in the U.S., I am kicking their ass to the curb. The next carrier may not be any better, but maybe if there's a mass exodus from AT&T when the iPhone is no longer exclusive they will consider changing their ways. Not likely, but the power to take our money elsewhere is the only power a consumer really has.

And now that I've said that, let me take a step back and say that AT&T is not the real culprit here. If you give a corporation the option of tacking on an additional fee that doesn't have to be advertised in the cost of their product or service or explained to the customer, they will use it, and almost certainly at times abuse it. Corporations are soulless entities that exist for a single reason: to make a profit. Individuals are motivated by other intangible things, such as "pride in craftsmanship" and "common decency", but those things are meaningless to a large corporation.

The real problem is the incompetent and mostly unnecessary FCC, which has become little more than Big Business's bitch. It's certainly not responsive to the taxpayers, but it is responsive to the big media and communication corporations. Sure, they occasionally waste taxpayer money "guarding the airways" against anything that might offend small-minded people in middle America, but their real mandate seems to be to help big media corporations make more money at the expense of the people who pay taxes to fund them. If the FCC's regulations were really designed to help the consumer, wireless providers would be forced to have a pricing model without inherent surprises and without intentionally planned nooks and crannies specifically designed to let the carriers steal extra profit. If you made a contract with a wireless carrier for $50 a month, you'd pay $50 a month plus, maybe, sales tax.

But, rather than say "Fuck you, FCC" in my own crass way, I'll let the far more eloquent Eric Idle say it in a moderately dated, but still very funny way:

Friday, July 3, 2009

An Exercise in Blatant Bias

I came across an interesting blog post today that purports to be a comparison of Android and iPhone development. And it is a comparison. While the author makes absolutely no claim that he is unbiased, a more fitting and less misleading title would have been A Comparison of iPhone and Android Development by Somebody Who Really, Really, Really, Really Loves Java and Doesn't Want To Be Bothered Actually Learning How to Use the Language and Tools Used in iPhone Development. Okay, I admit that that would be kind of an unwieldy title, but I can imagine a lot of people coming to this blog entry and getting a skewed view of things, so I want to provide a counter-opinion. I don't want to get into a pissing match, but some of the accusations in this posting are indefensibly wrong and tell us more about the author than the things he is comparing.

Let me put up front that I know some people will interpret my response as an attempt to say that Xcode is "better" than other IDEs. That's absolutely not what I'm trying to say. "Better" is a subjective matter. My goal is simply to counter the notion put forward in this blog post that Xcode is "shockingly bad" or that Xcode "takes away from the flow of a developer". It does take away from the flow of a developer who insists on trying to use it as if it was Eclipse or Visual Studio, but that's a flaw in the developer, not the in IDE.

But just like the original posting, this is a biased post based on my own tastes and likes, but unlike the original author, I actually have a comparable amount of experience with both of the things I'm comparing.

I think it was Jeff Atwood who said "the only intuitive interface is the nipple", and that couldn't be more true. Everything we're used to doing in computer programs - the entire paradigm is learned. That's completely true for IDEs and programming languages as well. We learn to use them, and many of us coders spend so much time in our IDE that they become second nature for us to the point where we see the way we work as natural.

One of the most common complaints I see from new iPhone and Mac developers , especially those coming from Visual Studio or Eclipse (like the author of the post linked above), is that they hate Xcode because it's missing this or that feature or it "feels dated". Only the most obstinate and stuck-in-their-ways developers continue to make those claims after working with it full-time for a year or more, however, because the problem isn't with Xcode, the problem is they were trying to do things in Xcode the way they were used to doing them in some other IDE. They were trying to bring to bear all their learned knowledge about how to program in Java or C# or Visual Basic in another IDE with different conventions than are used in coding Objective-C using Xcode.

Now, I'm not trying to say that Xcode is perfect. No IDE is perfect, and they are constantly borrowing ideas from each other. One of Xcode's virtues, in my mind, is that they haven't adopted the kitchen sink approach of trying to implement any feature that anybody thinks up. They've historically had relatively a small, focused user base, and the Xcode team has created a scalpel, not a chainsaw. Xcode is written by some really smart engineers who have to use it every day for writing C, Objective-C, and C++. They aren't using it to write VB or C# and only rarely to write Java. They have different needs and different priorities than the Eclipse team or the Visual Studio team. But to claim that Xcode is "lacking" or "bad" does those developers a grave disservice. It does not try to be everything to everyone, that is true, but it is nonetheless a great IDE for the types of development that its user base does on a daily basis.

Here's the thing. If you are constantly missing some feature from Eclipse of Visual Studio, there's a really, really good chance you are working against the grain, trying to do something based on things you think you know from another language or application framework, or that you simply haven't discovered some piece of functionality in Xcode.

David Green, the author of this blog post, make the claim that Xcode (which he has been working with for "a few months") impedes developer productivity and that it is therefore "inferior" to Eclipse, a program he's worked with day-in and day-out for twelve years. Yes, that sounds like a fair basis for comparison. "This thing I just started using isn't anywhere near as good as this thing I've been using for years and know inside and out."

You can get some insight into the thought process behind this claim from this quote:

Java is a no-brainer. I have to say that it’s nice not to have to learn a new language to target mobile. Existing skillsets don’t come easy — so reuse of expertise is worth a lot.

Now let me put out there that I have worked with Java since about 1997, which would be, what, say... twelve years? It was my bread and butter for close to ten of those years. I've also got about ten years experience with Cocoa, Objective-C and Xcode(if you include its predecessor Project Builder). So, I have some basis for comparison and let me state that my opinion of the languages and the IDE is almost exactly the opposite of Mr. Green's when it comes to mobile development of GUI applications.

For starters, Java is absolutely not a no-brainer. In fact, Java is a really, really poor choice for embedded development. Oh, I know Java has been pushed as a great choice for embedded devices for years, but it's just not.

Mobile devices are constrained resource devices. The overhead for Java runtime's memory and processor overhead is non-trivial on such a device. And "existing skillsets" may not come easy, but reuse of expertise is no good if the cost for doing so is to make poor choices. You've certainly heard the cliché "if your only tool is a hammer, every problem looks like a nail"? Well, re-read the quote above and tell me that it's not simply a restatement of that cliché as an axiom. He's stating "Java is better because I know it better", nothing more. I know both languages, and Java is a better choice for many tasks, but there's really no way you can claim that it's better for writing a GUI application on an embedded device. Or, for that matter, for writing any GUI application period.

On the other hand, Objective-C is a great choice for writing GUI applications for an embedded device. It has a runtime that provides a lot of dynamic functionality with considerably less overhead. Objective-C has far better introspection than Java and greater flexibility in terms of passing objects of different types through a responder chain without filling up your code with typecasts or constant use of godawful generics (a much-lauded hack to get around a fundamental flaw with strongly-typed static languages like Java when it comes to designing GUI applications).

Some other gems from this blog post:

One thing that really became clear to me is that Objective-C, though it may have been visionary for its time, is really a language of the '80s. Certain issues such as split header and implementation files and violation of DRY are really time-wasters, and not small ones at that.

I honestly had the opposite complaint about Java. I hated that there was only a single file per class. I hated that Java deprived me of the ability to do lots of cool things by separating out the implementation from what is advertised to other classes. I hated, for example, having to use static final variables and hated having to incur the overhead of a method call for tiny bits of functionality that could have been expressed in a precompiler macro or static inline function in another language. That's not to say that Java's approach is bad, just to say that just because it was thought of later doesn't make it inherently better. Its saves a little typing and a small amount of project clutter, but it comes at a price. Personally, I prefer having more flexibility and a few more files in my project.

Split header and implementation files provide extra functionality and the cost is relatively trivial. Navigating between header and implementation files can be accomplished with a configurable key binding. I switch back and forth regularly between header and implementation files. The key command becomes second nature after a little while and it becomes completely a non-issue. I can't even see how this would be an issue worth mentioning if you had spent a few minutes reading about Xcode's key bindings.

As far as DRY, must I really do 5 things to declare a property??

ZOMG! I have to release memory! And type in more than one place! Run for the hills!

For the record, I can implement a single property in about 20 seconds using Xcode codesense and key bindings to navigate between the header and implementation files. For those who don't type as fast, check out the excellent Accessorizer. Personally, I can't create Java getters and setters in Java using Eclipse noticeably faster.

Another thing to realize is that Objective-C has a garbage collector, and it's quite a good one at this point, but Apple has intentionally chosen not to support it on the iPhone. This isn't about a shortcoming in the language, it's about a conscious decision made to provide a better user experience. Forcing developers to take responsibility for memory means that well-written applications will be more efficient and use less memory. Sure, there's a risk of memory leaks, but there are ways to track those down, and garbage collection comes with a price. On a device like a mobile phone, that is a non-trivial price even if the garbage collector is pristine and doesn't have any memory leaks itself.

Java has a similar problem with properties, though not quite so bad — and the IDE helps you write your getter/setter.

Gosh, it's too bad we don't have anything like that in Xcode. It'd be nice if we had maybe a Design menu, or maybe some text macros for Objective-C. Or at very least, an extensible scripting architecture with provided scripts to help us write our accessors and mutators. It would be even better if we had a persistence framework that completely obviated the need for data model classes at all.

And for those who didn't catch on to my sarcasm in that last paragraph, we do have all of those with Xcode. There is a design menu with tools that help you design data model classes. Under the edit menu, there is menu item called "Insert Text Macro", with several common macros for all the languages Xcode supports, and there's also an Applescript menu with such gems as "Place Accessor Defs on Clipboard".

But, in the vast majority of situations, you don't need to actually write your setters and getters. I don't care if your IDE creates them for you, 95% of the time, those are still code clutter. I'd much rather have a property with a synthesize statement and possibly a release call than tons of cruft in my code.

We also have Core Data, which lets you design data model classes visually without ever creating an Objective-C class. Or you can create a custom subclass, but you only need to put in your custom functionality - no setters or getters unless they do something non-standard.

Another annoyance of Objective-C is the patterns that must be followed: implementing correct init and dealloc methods is non-trivial. @synthesized getters and setters for properties with retain should not be called in these methods. So many conventions and rules to remember!

Let me translate this: "Objective-C annoys me because it doesn't use the same patterns that Java uses and it is therefore bad". Implementing correct init and dealloc methods is non-trivial? How in the world could you possibly say that? If you understand the memory management contract (a whopping four whole rules), it's both trivial and intuitive. It only seems confusing if you haven't bothered to learn one of the fundamental aspects of the language you're using, and if you haven't, well, that's really not Xcode's fault, it's yours.

Objective-C’s imports and forward-declarations (@class) are a pain

What he calls a "pain", I call "more flexible". Forward declarations and header imports are more powerful and flexible than java's import mechanism, and if you understand what they are doing, they're really not difficult or time consuming.

With iPhone on the other hand, finding the functionality that I needed was painful. Classes and methods are poorly organized

Another translation: I kept looking for functionality as if I was working in Java and didn't find things where I expected them. Personally, I find Xcode's documentation browser (especially the new one coming in Xcode 3.2 under Snow Leopard) to be more intuitive and feel like it's designed perfectly for the needs of a knowledgeable Cocoa Touch programmer. It's not surprising that it's not designed to meet the expectations of a programmer learned from using another language, other frameworks, and a different IDE.

And here are some more complaints about the "shockingly bad" Xcode:

A decent window/editor management system. XCode and it’s associated tools (debugger) like to open lots of windows. Want to open a file? How about a new window for you! Very quickly I found myself in open-window-hell. The operating system’s window management is designed for managing multiple applications, not multiple editors within an IDE. It’s simply not capable of providing management of editors in an environment as sophisticated as an IDE.

Good point. It's just too bad there's no preference for changing to a single-window, or limited-window layout, huh? Oh, wait… there is, isn't there?

A project tree view that sorts files alphabetically. Really!

Oh, god, no. Please no. You're trying to use the project tree to do the detail panel's job. Just click the node of the tree you want, then in the detail panel, sort the part of the tree you've selected however you want, alphabetically, by code size, whatever. This gives you all the power of Eclipse's horrible project tree, but let's you only work with the section of the hierarchy you are currently interested in. Believe me, this is not a shortcoming in Xcode, this is a shortcoming in the way you're working.

iPhone app developers are given a pretty good UI builder. It does a great job of showing the UI as it will actually appear. It’s flexible and can model some pretty sophisticated UIs, so I was impressed. I found that using it was a little tricky — I had to read the documentation two or three times before I could really figure out how to use it properly.

Oh no - he had to read the documentation! No developer should ever have to do that. Anyone who damns Interface Builder with faint praise like this doesn't fully understand it and thinks it's just a form builder like they've used in Eclipse or VS.

Okay, I'm done. Now I'm being a little rough on the guy. He's giving an honest opinion thinking he's helping others, and that should be lauded. The problem is he's making claims that are not just a little biased, they're outright unsupportable. Calling Xcode "shockingly bad" because you haven't taken the time to learn how it works is just ignorant.

Okay, maybe a few more quotes, because there are so many good ones.

We may see a shake-up in the mobile market, with at least 18 new Android handsets being released this year. Until that happens, iPhone will remain a market leader and developers will have to put up with XCode and Objective-C.

The iPhone "will remain a market leader" until "18 new Android handsets" are released? Did you seriously write that with a straight face? You honestly think it's the number of handsets that is holding Android back? Seriously?

But at least he finished off with a great quote:

Sure, the iPhone is great — but can you install a new Linux kernel?

And the answer is, actually, yes. But who in their right mind would want to? Linux zealotry notwithstanding, the Mach kernel is better, and truth be told, most people - even most developers - don't want to waste time having to compile and install new kernels anyway.

Thursday, July 2, 2009

Core Data - Inserting a New Managed Object

For some reason, it really bugs me that the way that you insert a new managed object into a managed object context is by using a class method on NSEntityDescription. I realize that there is no "one right" abstraction, but every time I've been away from Core Data for a while, it takes me a while to remember how to create and insert a new object because this:

    NSManagedObject *newManagedObject = [NSEntityDescription 
insertNewObjectForEntityForName:[entity name]
inManagedObjectContext:context]
;

is completely non-intuitive for me. Although the entity description is used in the process of inserting a new managed object, there's no way you can claim it's the primary object for that action.

I may be wrong, but I think it was this way in EOF, too. Anyway, the nice thing about having a dynamic language like Objective-C that supports categories is that you don't have to live with things that don't fit your particular way of thinking.

To me, the logical place for a method that inserts a new managed object into a managed object context, would be an instance method on the context, though I could also see an argument for it being a factory method on the managed object as well.

When I'm writing my own Core Data apps, I use this category:

NSManagedObjectContext-insert.h
#import <Cocoa/Cocoa.h>
@interface NSManagedObjectContext(insert)
-(NSManagedObject *) insertNewEntityWithName:(NSString *)name;
@end


NSManagedObjectContext-insert.m
#import "NSManagedObjectContext-insert.h"
@implementation NSManagedObjectContext(insert)
-(NSManagedObject *) insertNewEntityWithName:(NSString *)name
{
return [NSEntityDescription insertNewObjectForEntityForName:name inManagedObjectContext:self];
}

@end


This very short little category allows me to insert new objects into a context by simply doing this:

    [context insertNewEntityWityName:[entity name]];

Which personally, I find a lot easier to remember than the first one.

Wednesday, July 1, 2009

What, Me Teach?

Yes, believe it or not, it looks like I will be teaching.

I've agreed to be the instructor for a three-day iPhone Boot Camp NYC Workshop that runs from August 14-16. Future workshops (both in New York and other places) are a possibility if I don't incite a riot or do a really horrible job teaching this one.

I'm actually really excited about doing this. The iPhone Boot Camp program is a great program and I'm looking forward to being a part of it. The ink is still fresh on the deal (I'm not even listed as an instructor on their web page yet), so I don't have many details right now, but I can tell you that this particular workshop is targeted at new iPhone developers who do have some basic programming knowledge (or, basically, the same people that our book is targeted at). So, if you live in or near the NYC Metro area and are interested in becoming an iPhone developer, I'd love to have you in my workshop.

If things work out well, I'm hoping to convince them to offer a more advanced workshop or maybe even an OpenGL / Gaming workshop in the future. Let me know if you're interested in either of those topics and, if so, where you live.

About the only other thing I know at this point is that I will be tweaking the syllabus that's on their website just a bit to update it for 3.0 and to match my own teaching style. Other than that, details will be forthcoming once I know them.

Mint Apps Promo Day is Today!

Min Apps, makers of Mint Nutrition, FallBall!, and Countdowns has organized a Promo Day for iPhone developers.

As a way to foster a sense of community among iPhone developers, they're giving away not only license codes to their own apps, but they've gotten several other iPhone developers to chip in promo codes as well. The only requirement to get the free promo codes? That you be an iPhone developer of some form. Codes are available here until they run out, so check it out!

Core Data - Determining if a Managed Object is New

Sorry for the dearth of OpenGL ES posts. Things are quite hectic now with writing More iPhone Development, and I'm not even fully caught up from the week spent at WWDC. So, it may be a little while before I'm able to get another substantive post out like another OpenGL ES tutorial.

Anyone working with Core Data may appreciate this short category I wrote, with some help from Jim Dovey. Curiously enough, managed objects don't know if they are new or not. That is to say, whether they've been added to the context since the last load or save. I've found this to be a piece of information I've needed a lot, so wrapped up the check into a category.

NSManagedObject-isNew.h

//
// NSManagedObject-IsNew.h

#import <Foundation/Foundation.h>


@interface NSManagedObject(IsNew)
/*!
@method isNew
@abstract Returns YES if this managed object is new and has not yet been saved yet into the persistent store.
*/

-(BOOL)isNew;
@end



NSManagedObject-isNew.m

//
// NSManagedObject-IsNew.m
//

#import "NSManagedObject-IsNew.h"


@implementation NSManagedObject(IsNew)
-(BOOL)isNew
{
NSDictionary *vals = [self committedValuesForKeys:nil];
return [vals count] == 0;
}

@end



Then you can just add these files to your project, #import the header file, and then you can ask any managed object if it's new by sending it an isNew message.

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