Friday, November 20, 2009

Goals and Achievements

Today marks the 1-year anniversary of the loss of my job. Well, technically, I lost it on Nov 21, 2008, but it was the Friday before Thanksgiving, so this is close enough. A quick glance back through relevant blog posts:

A few days after, no clue what I was going to do.
A month later, still toying with going back to engineering, just starting to mess with iPhone programming.
A couple months after that, bailed on engineering, unsure of where iPhone stuff will go.
Three months later, iPhone app available for sale.

Since I'm forging my way into a brand new world, I didn't really have any idea of what to expect. But I did have some rough goals in mind, so let's take a quick look at how I did.

In early March, I decided to join Apple's iPhone Developer program. The standard program costs $99 + tax, and is required to test programs on an iPhone, and sell apps on the store. My app, SlickShopper, was coming along well enough I felt this to be a prudent step. I had no idea what to expect in terms of sales, but figured I had mostly missed the gold rush that happened in the early days of iPhone apps. So my first goal was simple: recover my developer fee. With tax, that was $105.44. My app went on sale May 31, and my sales data says I hit $106.57 on June 27, so four weeks.

And I was thrilled!

Of course developers don't get this money right away. Apple pays out in certain minimums, and for a previous time period. So I would not actually get my first check from Apple until July 30. And due to the way Apple pays out for various regions, that first check was for only $86.10. This car is going to Vegas, baybee!

The next couple of goals are a bit intermixed, so bear with me for a moment. This will make sense in a few paragraphs.

Towards the end of March, I made the decision to attend Apple's World Wide Developer Conference (WWDC). This was something of a gamble at the time, as I still had no idea if iPhone development was going to be my future, and I was still quite a ways off from having an app ready to sell. I decided to go for it. In hindsight, worth every penny, and definitely put me on the path to where I am today. But, it was a big cost nonetheless. The conference itself isn't cheap, plus travel, hotel, and so on. So the next goal was to recover that cost.

Now it is important to state that my accomplishment of this goal is NOT due to sales of my app. Far from it; in fact to date the app has only brought in maybe 1/3 of that cost. I have supplemented that income in other ways. However, after being negative for most of the year, I did finally reach break-even in the first week of September.

Again, thrilled!

That supplemental income? Strange/funny story.

You would be hard-pressed to perform a Google search for anything iPhone development-related and not turn up a link on iPhone Dev SDK. I've found many answers there, and continue to do so. Sometime in July, I came to the realization that I actually had enough knowledge to answer questions for other people. I made an account, and soon was burning a significant amount of time responding to posts. I enjoyed doing it; it reminded me of the time I used to answer I-DEAS questions on the ICCON mailing list. And I loved being in a teaching mode. I seem to do my best learning when I'm teaching other people. So with a decent nudge from WWDC, time spent on iPDSDK really helped to refine and solidify my understanding of concepts I've been struggling with for a couple years.

One day, a clearly frustrated guy was lamenting that he could not find good answers. He had even offered to pay people $10/question, and didn't get good results. I jokingly responded that I'd be happy to answer questions for $10, not really expecting anything. I'd been answering lots and lots of questions for free, and probably would have in this case, too. But hey, if he wants to offer, I'll accept.

We emailed back and forth a bit, and he was able to look at my post history and determine that I had a decent chance of knowing what I was talking about. He sent me his code, I spent a couple of hours going over it and making notes, recommending better techniques, pointing out where problems were and how to fix them. I returned his code, expecting that to be the last I ever heard from him. A couple days went by, I hadn't heard anything, so I assumed my initial assumption was correct.

But wait! An email. He's having trouble sending me a PayPal payment. Uh huh. Sure. But he is not in the US, so let's go see what options are in PayPal... huh. Sure enough. Toggle that, change that. Ok buddy, try again. Another day or two goes by. Yeah, that's what I thought.

But wait! Another email. This time with a screen shot of his attempt to send me PayPal. And for $20! Back to PayPal options. I now accept money from anyone, anywhere, for any reason. The next day, I get money.

Let's back up a moment. I developed SlickShopper for me. I wanted it. It was my first app, my practice app, my test of "can I actually do this?" If other people are willing to buy it, great, but this is for me. But, I was not ignorant of the fact that it was also my programming resume. I don't know that I had any specific expectations, but in the back of my mind I thought it would be nice if this app would help me to land a new job. I guess I was thinking more in terms of a regular 9-5 job, but whatever.

So, someone else - excluding SlickShopper customers - paid me money for my efforts. I received that first payment on July 29th, beating my first check from Apple by a single day. I received the last severance check from my job in February, so my period of zero income ended at five months. It certainly wasn't Real Money yet, but anything is better than zero.

Things continued like this for several weeks. He'd ask questions, I'd answer, he'd send me money. It took me a while to grasp what I had stumbled into. In the middle of this, other people started coming to me looking for similar arrangements. In some cases, I'd already been helping someone in a public thread, and they'd decide they were willing to pay for individual attention. In other cases, people would browse the forums, land on some of my posts, decide I sounded knowledgeable, and ask if I could help.

Eventually I would take a more proactive role towards landing jobs. There is a jobs forum, and people will frequently post that they are looking for developers. Most of the ones I went after would turn out to be beyond my skill level, and I'd have to decline. But I'd get smaller ones, and gradually built up a small pool of regular clients. Now I'm doing everything from basic support to full-bore application development.

At this point, I'm happy to report that BriTer Ideas LLC has experienced 5 consecutive months of record revenue, and signs are looking good for the future.

I found my new job.

The ratio of contract services income : SlickShopper sales is so skewed it isn't even funny. I can make more in an hour doing work for other people than SlickShopper will make in a week. I actually have started on version 2, but I'm so busy with other things (read: actually making money) that I don't honestly know when I'll have time to finish it. And I feel bad, too, because the new features are things that I want but didn't know how to do for version 1.

I still have a ways to go in order to regain my former glory. I'm hovering at just under half of what my former salary was, so I'd like to find ways of bringing in more income. Exceeding my former salary is my next main goal. I feel like the potential is there, I just need to put myself into position to take advantage of it. Also, like most small/home business owners, I need to add on the stress of being concerned about from where the next job will come. That will be less of a concern once my wife finds a new job, but for the moment it's nerve-wracking.

Otherwise, I'm having a blast. I can't remember the last time I had this much fun in engineering, if I even ever did. I literally do work 7 days a week. I won't pretend that I'm 100% productive each day, but I've been hitting it hard for months and don't feel like I'm forcing myself to do it.

A year ago, I said:
"One way or another, 2009 should be an interesting year."

I was right. And I'm really looking forward to 2010.

Saturday, September 12, 2009

Subversion Basics In Xcode

My last post discussed how to set up Xcode to use Subversion via Beanstalk. Now let's take a look at some common activities.

Editing Files



This is easy enough... make some changes to a file, then save it.



The SCM column will provide status information when appropriate for each file. In this case, 'M' is shown, indicating that the file has been modified. You could see any number of M's, depending on how many files you change between commits (more on commit in a moment). See the Apple documentation for a complete list of possible symbols.

Adding Files



Whether you create a new file inside your project, or add existing files from elsewhere, the end result is the same:



The SCM column will display a '?', and as seen from the help pop-up, that means the status is unknown. This indicates that the file is not yet in the repository - it is in your local project only.

To indicate that you would like to add these files to your repository, right-click on any appropriate files, and choose 'Add to Repository'.



The '?' will change to an 'A'.



This indicates that the file will be added to the repository during the next commit.

Committing Changes



I am more accustomed to "check in" terminology, but a commit is exactly the same thing. Local changes will be uploaded to the repository.

Go to the SCM menu, and choose 'Commit Entire Project...'.



I believe you can do commits on individual files, but I haven't had much of a reason to do so. I pretty much always commit the entire project.

Xcode will present a dialog box allowing you to enter notes related to this commit.



Once you are done, the M's and A's should all be gone, indicating the files are now in the repository.

How often you commit is up to you. I personally attempt to do so at minimum nightly for any files that I work on. If I achieve some major milestone earlier in the day, I'll send up an extra commit.

Updating Files



If other people share your repository, or if you access it from multiple computers, you will probably see this at some point:



The U indicates that the file in the repository is newer than your local file, and it needs to be updated. As a convenience, Xcode will repeat the status indicator on the group folder, just in case you are not displaying all files at the moment.

You can either right-click on individual files and update...



...or you can update the entire project.



And this concludes what I know about Subversion. Hopefully these two posts will get you off to a good start. Enjoy.

Getting Started Using Beanstalk With Xcode

I have lived in a data-managed world for years. I-DEAS has long distinguished itself as one of, if not THE, only CAD programs with an integrated data manager. So I am quite comfortable with the concepts of access control, version control, and pretty much any other -controls that go along with data management.

When I first got started coding, I quickly realized that it sure would be nice to have the backups that go along with regular check-ins into a managed database. I did some poking around, and it sounded like Subversion was a pretty safe way to go. But as far as actually implementing it, well, that sounded like a bigger hurdle than I was willing to tackle at the time.

I don't remember the specifics, but I was doing some Googling for hosted Subversion services, and landed on Beanstalk. I knew nearly nothing about it, and don't know much more today, but most importantly they offer a freebie account. It's a nice way to try out Subversion; it doesn't cost anything other than time.

The question then became how do I get the work I've already done into Beanstalk, and then how do I use it? If there is a handy guide out there, I didn't find it. So allow me to present my attempt at providing that guide.

I should preface this by saying that beyond initial setup, I don't know a whole about Subversion. I'm only working by myself; I'm not trying to coordinate efforts with a whole team of people. I don't know how to branch. I really only use it to commit my changes regularly, and if necessary revert back to an older version of something.

Preamble out of the way, let's begin...

Repository Configuration



Once you have created your Beanstalk account, using your web browser go to the Repositories tab.

Click on the desired repository

Then copy your repository URL.

You are now finished with your web browser, as pretty much everything else is done in Xcode. So, fire it up.

You will be using the SCM menu a lot, so find it now, and select Configure SCM Repositories.

This takes you into Xcode's preferences under the SCM tab.

Here you can see that I have several repositories already. Click the + button at the bottom of the Repositories list to create a new one. Keep your eye on the green dot that says 'Authenticated', as that will be important.

Xcode will prompt you for a name for this repository, so call it whatever you want. This does not need to be the same name as your project, but you will probably want it to at least be related. Select 'Subversion' for the SCM System.

We now have a new blank repository.

Notice that our green dot is no longer a green dot. By the time we're done, it should be, and that's the indication that everything is set up correctly.

If that Beanstalk URL isn't in your clipboard anymore, go back and copy it. Paste it into the URL field. Any time you make a change to a text field here, tab out of it to see the result.

Notice that the scheme, host, and path were automatically determined from the URL. And our green dot is now an angry red dot. Fear not, you merely need to provide your username and password. Don't forget to hit tab after each text field change.

Once your information is correct, you should have a green dot. If not, verify your login and password.

Initial Upload



At this point, Xcode can talk to Beanstalk. However, there aren't any files in there yet to talk to. You can exit the Preferences panel, and go back up to the SCM menu and hit Repositories.

You'll see a listing of your repositories, and if you select one, you'll see the files and folders contained therein. This is looking at Beanstalk; these are not local files. The existing folders - branches, tags, trunk - I believe are relatively standard for Subversion.

We are interested in the trunk folder, so select that.

To add your project, hit the 'Import' button.


Select the folder that contains your project.

If you want, enter in a comment indicating what you are doing. But since this is the first time you are doing anything, it should probably be pretty easy to figure out later.

After you hit 'Import', Xcode will begin uploading your local files into Beanstalk.

You can click around to verify that all of your files are there.

Initial Download



I don't know if this next part is actually required, or if you can continue working with your local files. For the sake of this blog post, we're going to assume it is required. If you poke around in Subversion documentation, you may see the phrase "Working Copy". This refers to the local files on your hard drive that you have retrieved from Subversion. So, we need to create that working copy.

In the Repositories window, select the same folder that we just uploaded. Then hit the 'Checkout' button.


Xcode will prompt you for a download location. I personally use my Dropbox folder for an additional layer of protection.

When the download is complete, Xcode will provide an option to open that project. Go ahead and do so, because we aren't quite done yet.

Final Setup



Once the project is open, right-click on Groups & Files, and turn on the SCM option.


This will reveal a new column that displays status information about the files.



There isn't much to see yet, since we haven't done anything. But the key thing to notice is that the SCM icon has an X through it. I don't really know why, but there is one thing left to do. Select the project file, then right-click, Get Info.



This will take you to the project info panel. I'm taking these screenshots using Xcode 3.2, and this panel did see some slight modifications, but the core element we're looking for is still there in previous versions, you just may have to poke around for it a bit.

Select the 'General' tab.

Then hit the 'Configure Roots & SCM' button.

Make sure the pull-down menu says Subversion. In the Repository column, hit the pull-down there to select your repository. The name will most likely have "Recommended" next to it. I honestly don't know why this step is necessary; Xcode seems to know this stuff already, so you'd think it could set itself up. Oh well.

Once that is done, the X should disappear from the column's icon.


At last we are set up!

Come back later for part 2, at which point I'll discuss some usage basics.

Thursday, September 10, 2009

Taming Table Views

This is the table view from one of the primary screens in SlickShopper.



It may not look like much, but there is actually a lot going on here. The key aspects I want to talk about now are the sections headers and the index. More specifically, what the presence of those items means as far as preparing the data that is to be shown in the table.

Naturally I have a complete list - an array - of all grocery items. However, as soon as I made the decision that that I wanted section headers, I could no longer use that list directly. Well, that may not be technically true, but from a "reasonable effort" standpoint, true enough. In order to display section headers, the table view data has to be broken up into groups, with each group corresponding to a section in the table.

The number of sections is defined using the table view data source method:
– numberOfSectionsInTableView:

I've got "selected" and "not selected", so all I need to return here is 2. So far so good. I need 2 arrays, one for selected, one for not selected, and I need to run through my master list and add each grocery item to the appropriate array. The remaining tableview delegate methods are straightforward; needing only to ping each array for information. Easy.

...until I decided that having the index on the side would be a really useful thing. Why does that matter? Well, the first clue is in the method name that provides the index:
- sectionIndexTitlesForTableView:

Index titles for a section. Well, at the moment I only have 2 sections, but I'm going to want potentially almost 30 items to appear in the index. Although you don't technically have to have a 1:1 section:index title ratio, it's a lot easier if you do. When tapping the letter 'G' in the index, you want to navigate to items starting with the letter G, and that is most easily accomplished by grouping those items together, and having that be a section in the table.

So now I'm looking at a whole mess of arrays: one for items starting with A, one for B, and so on. Not a problem, I can add each of those separate arrays to one main array, and go from there. Oh, but if no items start with a particular letter - say, Q - I don't want to show the letter in the index. My array contains 24 arrays, and I'm looking at the 6th one, so which letter should appear in the index? F? What if there were no E's... it could be G. Ugh. Now I need another array to keep track of index letters. I suppose I could grab grocery items and extract their first letter on the fly, but that has some issues (ex: I want to display "#" instead of "1", "2", etc). I've already got my array of arrays to provide the contents, now I have another array for the letters, but I think we're almo... aw, crap... The section headers! The screen shot above is only showing 2 headers, but there are indeed many sections involved. And I have a different view of this same information that uses many headers. Now I need some means of keeping track of the headers - maybe another array? I've got dozens of arrays now, each keeping track of different-but-related pieces of information... stop the insanity!

After spending way too much effort doing exactly that, I finally hit upon what I consider to be a better way: a custom container object. Something that can correlate to the sections in a table, and contain the necessary pieces of information all in one place: the section contents, the section header, the index letter, and for bonus points some handy methods for messing with all of that stuff. So, I now present what I call a DisplaySection:

//  DisplaySection.h
// SlickShopper
//
// Created by Brian Slick on 4/5/09.
// Copyright 2009 BriTer Ideas LLC. All rights reserved.
#import Foundation/Foundation.h (Yeah, I know)
@interface DisplaySection : NSObject
{
NSString *myName;
NSString *mySectionHeader;
NSString *mySectionIndexLetter;
NSMutableArray *myContents;
}
// A few sample methods...
- (void)addGroceryItem:(GroceryItem *)item;
- (NSUInteger)countOfGroceryItems;
- (GroceryItem *)groceryItemAtIndex:(NSUInteger)index;
@end


There are a lot of ways to tailor this to individual liking. If you like _instanceVariable names, do that. If you want to make this generic with things like addObject, or countOfContents, do that. If you want to access the properties directly without going through wordy method names, do that. Those are implementation details that do not affect the core concept.

The point here is that everything I want to know about my section is here in one place: contents, section header, section index letter. On top of that, I found it necessary to add an extra property for behind-the-scenes purposes.

How does this work? Well, I don't want to give away all of the family jewels, but in essence most of the sorting work that I described before still has to happen. I still have to iterate through my master list of grocery items. I still have to have a place to put each item, so that means creating a bunch of these DisplaySections, and filling them with appropriate content. And I want these to be presented nicely for the table, so that means putting a bunch of these DisplaySections into an array.

So what's really different? For one thing, I've moved all appropriate logic into the sorting routine. Take the section header, for example. Before, I was defining that in the table delegate, making decisions based on the index path. If section is 0, header is this, else if section is 1, header is that, and so on. Those decisions still have to be made, but I no longer do that in the delegate methods. As I iterate through the main array, I make those decisions at that time, and react accordingly. This is my first DisplaySection, so the header property should be set to this. This DisplaySection will contain all items starting with H, so set the index letter accordingly. All of the logic is in one place. If I want to change behaviors, that's where I go.

A side effect of this is that it simplifies the delegate methods tremendously. Here is how the header gets returned:

- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section
{
return [[[DataController sharedDataController] displaySectionAtIndex:section] sectionHeader];
}


(I'm using a singleton object for the data controller... go here for more info)

I can assure you that this was more than one line of code previously. Now I ask the sorted array for the appropriate DisplaySection, and I grab the needed piece of information. The logic is removed from the delegate, and it merely does what it is supposed to do - relay information from the model to the view.

For the index letters, I elected to spin my own array in the delegate method. I really wish Apple had gone with more of a:
- (NSString *)tableView:indexTitleForSection:

approach, but they didn't. So I just loop over each DisplaySection, grab the index letter property, and build a new array in:
- (NSArray *)sectionIndexTitlesForTableView:

There is probably a better way, but this covers my needs. I'm pretty sure that this method is only hit once during a reloadData, so I don't consider it much of a performance hit.

How do I handle the case of nothing-starts-with-that-letter? That's part of the sorting routine. I actually go ahead and build the maximum possible number of DisplaySections up front, and add them to the array. Then I loop through the grocery items, and add them to the appropriate DisplaySection. After that is done, I loop back through the DisplaySections, and remove any that are empty.

Once I had this technique down, it was a lot easier for me to go back and add the index to several more of my views. So I'm very happy with this approach, but I'm always interested in suggestions. Have a better way?

Monday, August 17, 2009

It's NSLog, NSLog...

What rolls downstairs, alone or in pairs, and over your neighbor's dog?
What's great for a snack, and fits on your back? It's NSLog, NSLog, NSLog!

I do crazy amounts of logging in my programs. This has proven to be an invaluable tool for me as I learn how Cocoa/Cocoa Touch works. Being relatively new to Object-Oriented Programming, it has taken me a while to wrap my head around when methods get called, in what order, and so on. So I borrowed a technique I was using in the I-DEAS macro language in order to help me figure out what was happening:

NSLog(@"Now entering viewDidLoad in RootViewController");
// Do stuff
NSLog(@"Now leaving viewDidLoad in RootViewController");

The console will then present a nice history of what the program did. This is useful, especially for things like UITableView delegate methods, where simply putting the view on the screen can cause several methods to be triggered. Read back through the console, see which methods were called, note the order, learn how the system works.

I do both the 'entering' and 'leaving' part because simply putting the 'entering' one does not provide enough information, especially when trying to track down where a crash happened. A given method could branch out into any number of other methods, so without knowing when a method has finished, you don't really know which one crashed. You can make some partial assumptions by figuring out if expected methods were NOT called, but still, you don't get the complete picture. By adding the 'leaving' message, it's a matter of figuring out which method(s) started, but didn't finish.

The information provided by this technique is incredibly valuable, but it has (at least) two key drawbacks:

1. The log messages themselves must be customized for each method, in each class. You can get away with some copy-n-pasting, but you are going to have to retype something every single time. That's annoying, time consuming, and a good source for mistakes (if you forget to change the information).

2. The performance hit from displaying this many log messages is considerable, particularly on the iPhone. It's useful for debugging, but is not something you want to do for shipping code.

It's NSLog, it's NSLog... It's big, it's heavy, it's wood.
It's NSLog, it's NSLog, it's better than bad, it's good!

I was doing this - what I'll call "the hard way" - for while before I decided there had to be an easier way. I did some Googling, and landed on this page. There are several good tips there, but this is the important part:

NSLog(@"%s", __PRETTY_FUNCTION__);

Will print a log line similar to:

*2008-12-12 09:22:49.552 Healthcheck[79016:20b] -[HealthcheckAppDelegate applicationDidFinishLaunching:]*

Perfect. That is copy-paste-able, no additional typing required. However, I still want my entering/leaving deal, so adapting this to my preferred style:

NSLog(@">>> Entering %s <<<", __PRETTY_FUNCTION__);
// Do stuff
NSLog(@"<<< Leaving %s >>>", __PRETTY_FUNCTION__);

And I do this everywhere. Every single method. I'm not kidding.

Experienced programmers reading this are thinking I'm pretty ridiculous right now. But I need the extra help. And I highly, highly recommend this for people just getting started. I've been answering a lot of questions on iPhone Dev SDK, and am amazed at the number of people who know nothing about logging. And I don't mean noobs, I mean people with apps on the store! I'd probably still be floundering around with my very first bug without liberal use of logging. So, my hat's off to people who can get by without, but I have to assume that life could be easier for them.

Everyone wants a NSLog
You're gonna love it, NSLog

So that addressed the first problem, but what about the second? Fortunately, I got an answer to that without really even asking the question. After finding the pretty function thing, and posting on Twitter about how awesome that is, Danilo Campos was kind enough to make an off-hand remark that I should disable my log messages before submitting my app to the store. I had no idea how to do that, so with a couple of generous emails, he got me squared away. So full credit goes to him for this technique.

First, your program should have a *.pch file in it somewhere, named something like <YourAppName>_Prefix.pch. In my case, this file is SlickShopper_Prefix.pch. Add the following to that file:

#ifdef DCBLOCKNSLOGSTATEMENTS
#define NSLog(format, ...)
#else
#define NSLog(format, ...) NSLog(format, ## __VA_ARGS__)
#endif

The 'DC' part is for Danilo Campos, so if you want to change that, you can. I've decided to leave it alone, along with a commented note giving him credit. Now I won't claim to totally understand what this says, but it looks like if DCBLOCKNSLOGSTATEMENTS is defined, do something to NSLog statements, if not, do something else. In this case, the 'do something' part is to make the NSLog statements not do anything, and the 'do something else' case is leave them alone. So that's the easy part. The harder part is getting this to work.

What you need to do now is mess with the settings in your build configurations panel. To do this, select your project, and hit the Info button, like so:

Next, go to the 'Build' tab, select the "Release" configuration, and do a search for "preprocessor". Ex:

You are looking for the ones listed under "GCC 4.2 - Preprocessing" - the "Preprocessor Macros" one and the "Preprocessor Macros Not Used In Precompiler" one. I occasionally cannot find these items. The solution seems to be to go to the 'General' tab. There is a pull-down menu next to "Base SDK for All Configurations". Set that to "iPhone Simulator 3.0", and then try searching again.

It is very important that you select the Release configuration. Do not do this in Debug. We're about to kill NSLog statements; we want those in Debug.

Now, double-click in the 'Value' column field next to Preprocessor Macros. A sheet will slide in.

Click the plus button to add a new value.

Paste in "DCBLOCKNSLOGSTATEMENTS" (or whatever you called it).

Hit OK...

...and then do the same thing for the second item.

And that's it. Just as a quick check, flip over to the Debug configuration:

You do NOT want to see the items we just added here.

If you have additional configurations already, say one for beta-testing and another for App Store submission, you will probably want to repeat these steps for those configurations as well.

Now test it out. Build-and-go with Debug, and observe your log messages. Do it again with Release, and observe the lack of log messages, and the snappier performance.

Next, head over to Danilo's web site and buy some of his apps as a thank you for making this technique known. And uh, if you want to buy mine for typing this up, well that'd be nice, too.

Have a better way? Let me know in the comments.

Everyone wants a NSLog. You're gonna love it, NSLog
Come on and get your NSLog. Everyone needs a NSLog
NSLog NSLog NSLog!

Monday, July 20, 2009

Now Presenting... My iPhone Presentations

The Mobile Mini-Conference has come and gone. I was not told whether Awesome Inc would be providing the presentations after the fact, so I'll just short-circuit the process and make mine available now. Here you go:

Feel free to contact me with any further questions. Contact info is at the end of each presentation.

Sunday, July 12, 2009

Now Presenting... Me

Awesome Inc is hosting what they are calling Mobile Mini-Conference at their facility in Lexington, KY on Saturday, July 18th. This is ostensibly a generic mobile device conference, but as the schedule demonstrates there will be a particular focus on the iPhone. Here is a fancy badge:

Awesome Inc. Mobile Conference

I have been invited to speak, and will be giving two presentations. They are:
  • UITableView Overview

  • iPhone Development Resources, Tips, and Tricks


I hadn't even started SlickShopper 6 months ago, so if you had told me then that I'd be giving iPhone presentations now, I'd have thought and said you were crazy. But here we go. It's tough to do much justice to any development topic in 30 minutes, but I think my presentations will be worthwhile.

I'm not really nervous, per se, as I've done plenty of presentations before. However, this will be the first in a long time where I'm presenting on a topic where I don't have a solid and lengthy background. I'm also accustomed to practicing my presentations on my coworkers, but I don't have any of those at the moment. I'll do fine on the speaking part, but I'm not looking forward to fielding questions. Whenever I talk about I-DEAS, I'm among, if not THE, most knowledgeable subject experts in the room. I'll be shocked if that is the case with iPhone development, and a couple of well-placed questions could shatter the illusion that I know what I'm talking about. But, this is a brand new conference, so I don't really know what to expect.

So at any rate, if you are within a reasonable drive of Lexington and have any interest in iPhone development, come check us out.

Thursday, July 02, 2009

Rethinking The Problem

I believe this is officially the first of what will hopefully be many technical posts about my iPhone program SlickShopper. I'm just about to release a semi-major update, and a couple of revelations I had along the way are, I feel, deserving of some attention.

The Reason



SlickShopper doesn't break any new ground in the area of user interface. It is a simple, table-driven interface that uses built-in widgets almost everywhere. But the actual table cell - the row of information that is displayed - is a key area where I needed to provide something a little beyond what Apple includes out of the box. Here is a sample that is typical of my three main list views:



It's not super fancy, but for sake of this discussion, the important thing to notice is the number of pieces of information. There is the 1) description, 2) quantity, 3) cost, and 4) checkbox. So far, so good.

I am a big believer of options in software. As a user, I generally want options, and as a developer, if two reasonable people can see two reasonable ways of doing something, then if possible I'd like to support both people. And I carried this philosophy into this program. Except for the description, each other piece of information can be disabled. If you don't care about costs or quantities, you can turn them off. I personally can't stand having the checkboxes there (they are great looking; don't get me wrong... but I don't like having the second touch zone that they provide), so I turn those off, too.

The more math-oriented readers might already see the factorial that is forming here. I have 3 items with different display possibilities, so right now I have 3! = 3x2x1 = 6 variations. But I'm not done yet. I do have one display mode where I need to show the checkboxes, even if the user has turned them off. I need them for visual distinction between selected and not-selected, but I just want them for display; I don't want the second touch zone. So add one more option, and now I'm at 4! = 4x3x2x1 = 24 possibilities. It actually isn't this bad; due to some overlap of the options, it turns out that I really only have 12 scenarios to handle. For example:
checkboxes ON, quantity OFF, price ON; or
checkboxes ON, quantity ON, price OFF
...and so on. The logic for determining which scenario I'm in is going to be convoluted pretty much no matter what, but what about displaying of the actual cells?

The First Attempt



This would have been much simpler without the checkboxes. I've had enough trouble getting my head wrapped around the use of Interface Builder for iPhone development as is, but I couldn't find any examples that included a button in the cell. The only examples I could find built the cells in code, so that's what I did. I'm not going to bother laying out the code for them, but I'll list a few of the file names just to give an idea of what I was doing:

CompleteListCell.h
CompleteNoCheckListCell.h
CompleteImageOnlyListCell.h
MinimalListCell.h
MinimalNoCheckListCell.h
MinimalImageOnlyListCell.h

So first I decide how much information is being shown, based on the preference settings. If costs, prices, and checkboxes are all turned on, then I use the CompleteListCell. If the user turns off the checkboxes, then I fall back to CompleteNoCheckListCell. BUT, if the user then chooses a display mode where I need to show the checkboxes, I swap in the CompleteImageOnlyListCell, which replaces the button with an image.

This approach certainly does work, and is what shipped with my 1.0 and 1.1 versions. When I originally planned this out, I came up with 12 variations, so I have 12 of these files. Making them was easy enough; I built the most complicated one first, then pretty much just copy-pasted to make the others, removing code that no longer applied. And as long as I don't ever need to change anything (HA!), these files will be just fine. But if I decide to, say, change the font used for the description, I have to make that change in 12 places. And if I decide to add more information to my cell, I'm going to need a crapload of new files. So, this approach is workable, but ultimately doomed.

Duh



The first revelation concerns the checkboxes. What normally happens when a user taps on a row is that tableView:didSelectRowAtIndexPath: is called, and in the method you decide what you want to do in response to that tap. In this case, I want to slide in a detail view:



Easy enough. However, the whole point of having the checkbox is to provide an alternate action. If the user hits the checkbox, I want the row to animate out, like this:



I have the checkbox pointed to a buttonPressed: method, where I do the necessary steps to animate out the row.

The stumbling block was when I needed to show the checkboxes, but I didn't want them to function as buttons. So instead of imbedding the button into my table cell, I chose to make extra cells that contained the checkmark image. And then based on the appropriate preference setting, I chose which cell to display. I was thinking about this far too literally.

It suddenly occurred to me that I don't need to care about what kind of physical item - button or image - is being displayed, I only care about what happens when it is touched. If it is an image, normal row selection should happen, if it is a button, something extra should happen. But how do I tell the difference if I don't use a physically different item?

The answer is so blindingly obvious (in hindsight, of course) and simple that I'm really mad I didn't figure it out sooner. And that answer is the buttonPressed: method. I already have a method for declaring what should happen when the button is tapped, so what I need to do is make a small change to it to accommodate both of my needs. Here is the code:


if (![userDefaults boolForKey:kPrefsCheckboxesKey])
{
[self tableView:[self tableView] didSelectRowAtIndexPath:indexPath];
return;
}


First, I figure out what the preference setting is. If the checkboxes are turned off, I redirect to the exact same method that would normally be called when a row is tapped, and then bail out of the rest of the method. The end result appears to be only a single touch zone for the user, even though technically it is still two. I get my desired behavior, and this lets me remove the CompleteImageOnlyListCell.h cell and equivalent variations. So, this realization alone allowed me to go from 12 to 8 cells.

Don't overthink the problem, kids.

A Better Example



I didn't get super motivated to evaluate my cell structure until I saw this post on the Cocoa-dev mailing list by mmalc Crawford. I had glossed over it originally, not realizing it was actually an iPhone question. But sure enough, down under the 'Replicated content' section he provides instructions for using Interface Builder to graphically build cells that contain buttons. I believe that mmalc is one of Apple's tech writers, and have since found almost the exact same explanation in the "A Closer Look at Table-View Cells" section of the "Table View Programming Guide for iPhone OS". There is also a relatively new example called TaggedLocations that demonstrates the technique.

So, just like the first go-around, I laid out the most complicated version first, just to see if I could make the technique work. Here is what that looks like, with color added so that you can more easily see the different UILabels involved:



There really isn't too much code going along with this cell. This is my header file, and the implementation doesn't contain a whole lot more. It pretty much just synthesizes the accessors, inits, and deallocs.


@interface CompletePortraitListCell : UITableViewCell
{
UIButton *checkButton;
UILabel *primaryLabel;
UILabel *leftSubLabel;
UILabel *rightSubLabel;
}

@property (nonatomic, retain) IBOutlet UIButton *checkButton;
@property (nonatomic, retain) IBOutlet UILabel *primaryLabel;
@property (nonatomic, retain) IBOutlet UILabel *leftSubLabel;
@property (nonatomic, retain) IBOutlet UILabel *rightSubLabel;

@end


So that's good, but doesn't really help me a whole lot yet. Don't I need 7 more of these? As it turns out, no, this is the only file I need, and it can handle all 8 of the remaining variations.

I should probably take a moment to say that everything I'm about to achieve probably could have been used on my older code-only cell, too. But I do feel confident in saying that it probably would have been a bit more complicated to do so. And I can make size and position changes in IB so much more quickly than I can in code.

We begin with the complete cell as shown before, and let's take a quick look at it running on the phone to make sure it works:



The first option I want to handle is when the user has elected to display only one piece of additional information - quantity OR cost, but not both. I made the decision early on that in this case, the piece of information would be displayed at the left (green box), regardless of which piece of information it was. I can redirect the text into either box, so that's not hard. And it turns out that choosing not to display a box isn't hard, either. UILabel inherits from UIView, which in turn has a hidden property. So, after initializing my cell, all I have to do is:

[[cell rightSubLabel] setHidden:YES];


This results in:



Or, if only cost is being displayed:



So if hiding can make things go away, maybe I can do that with the button, too. And I can:

[[cell checkButton] setHidden:YES];


...which results in:



So the checkbox is gone, but now the text looks funny dangling out in space. I need to shift the text over to occupy the space vacated by the checkbox. This is accomplished by messing with the UILabel's frame. First, I'll grab the existing frame for the description label:

CGRect primaryFrame = [[cell primaryLabel] frame];


Then I'll feed that frame right back into the label, but make an adjustment to the X coordinate:


[[cell primaryLabel] setFrame:CGRectMake(primaryFrame.origin.x - 30,
primaryFrame.origin.y,
primaryFrame.size.width,
primaryFrame.size.height)];


Let's see what that does for us:



Well, we did succeed in pulling the label to the left, but the entire label moved. With short descriptions, the user might never notice the difference, but once they get some longer titles, they'll see their text getting truncated at an odd location. So at the same time we're moving the box to the left, we need to make the box wider to fill up the space. So, with a minor correction to the previous code, we now do this:


CGFloat adjustment = 30;
[[cell primaryLabel] setFrame:CGRectMake(primaryFrame.origin.x - adjustment,
primaryFrame.origin.y,
primaryFrame.size.width + adjustment,
primaryFrame.size.height)];


...and check our results:



Bingo. We can do the exact same thing with the green box. The blue one doesn't need to move in this case.

Speaking of moving a text box, that can probably handle the case where prices AND quantities are turned off. All that needs to happen there is for the description label to move down. Since we don't want the box to get taller as it moves, all we need to do is increase the Y coordinate:


CGFloat adjustment = 8;
[[cell primaryLabel] setFrame:CGRectMake(primaryFrame.origin.x,
primaryFrame.origin.y + adjustment,
primaryFrame.size.width,
primaryFrame.size.height)];


And of course hide the two detail boxes:

[[cell rightSubLabel] setHidden:YES];
[[cell leftSubLabel] setHidden: YES];


...resulting in:



At this point, the only differences between selected and not-selected are the checkbox state, and the color of the text. We'll stay with the black for selected items, so for unselected items we'll change the text color:

[[cell primaryLabel] setTextColor:[UIColor grayColor]];


The checkbox is similarly easy. In Interface Builder, select the button, and then in the Inspector set an appropriate image for "Default State Configuration", and the checked image for "Selected State configuration". After that it is simply a matter of feeding a boolean in for the state, which in this case is based on a property of my data model:

[[cell checkButton] setSelected:[currentItem isSelected]];


So after all of this, we are left with two cases: 1) not-selected item, no checkbox, 2) selected, but all options (checkbox, prices, quantities) turned off. Technically, our same cell could handle this. Set the checkbox to hidden, make the appropriate transformation to the frame coordinates, and we're there. But, this may perhaps be overkill. After all, we're talking about a cell that contains only a single line of text. That's a basic UITableViewCell. No reason to reinvent the wheel for that. Set the font size and color, done.

And as an added bonus - and my real reason for messing with my cells in the first place - this all works in landscape, too:



There's a little sneak peak at SlickShopper 1.5 for loyal readers.

Yeah, So What?



What did I actually accomplish? The key difference is that I went from 12 custom cell classes to only 1 (plus 1 built-in cell). That's less code to manage. And the new custom class is designed in Interface Builder, which means even less code. Less code is always good (less to break). And by having all of the important cell design in one place, that means a one-time change can have a wide-ranging impact.

I'm sure at some point I'll learn about a reason not to have done this, but for now it's working just fine. The only real weakness I can see at the moment is when I move the labels in code. Right now the transitions are all relative, so if I move the red box in IB, then the red box will also move in the simplified display. I'll have to make corrections to the code at that point. I could hard-code the destination coordinates, but then I'm really putting a wall between the nice stuff I now have in IB and what is happening in my code.

But for now, I'm happy, proud of myself, and grateful to mmalc.