Upcoming and OnDemand Webinars View full list

Good iPhone Practices

Joe Conway

The iPhone SDK has now been around long enough where we can start to pick out good practices in using some of the more “fuzzy” areas. There are two small, but important, practices that can make your life much easier.

###
Buttons in UITableViewCells

Sometimes, you will want to have a button or some sort of control in a UITableViewCell subclass. You could have many rows, and each one has a button, and that button should have a different result depending on what row it is in.

In sticking with the MVC paradigm, how do you accomplish this? You have to be able to set the target-action pair for these buttons and you also have to decide which row’s button was pressed.

Well, you don’t even have to look at the UITableViewCell, you are really only concerned with the button. A UIButton (or any UIControl) is a subclass of UIView. Every UIView has an integer instance variable called tag. You can use this tag to specify the row the button is currently in.

  1. When a UITableViewCell is created (and only when it is created), you add a target-action pair from you UIButton to your UIViewController subclass to call the method you want.
  2. When a UITableViewCell’s data is prepared, you set the tag of its button to the row that cell is occupying.
  3. When the action message of your UIButton is sent to your UIViewController subclass, you simply grab the tag of the sender to determine what row it is.

Your UITableViewCell subclass interface should look like this:
`

@interface ButtonCell : UITableViewCell
{
    UIButton *button;
}
@property (readonly) UIButton *button;
@end

`

And your cell retrieval method should look like this:
`

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ButtonCell *aCell = (ButtonCell *)[tableView 
            dequeueReusableCellWithIdentifier:@"ButtonCell"];
    
    if(!aCell)
    {
        aCell = [[[ButtonCell alloc] initWithFrame:CGRectZero
                                   reuseIdentifier:@"ButtonCell"]
                                        autorelease];
        [[aCell button] addTarget:self 
                           action:@selector(buttonPressed:)
                 forControlEvents:UIControlEventTouchUpInside];
    }
    
    [[aCell button] setTag:[indexPath row]];
    
    return aCell;
}


And finally, your action message should do this:

- (void)buttonPressed:(id)sender
{
    int rowOfButton = [sender tag];
    [[internalData objectAtIndex:rowOfButton] performFunStuff];
}

`

All there is to it.

###
Instantiating UIViewController subclasses

XIB files are confusing. Chaining UIViewController XIB files and creating instances of them in another XIB file is even more confusing. And quite pointless. There is a much better way to do it.

First, you have to decide if you want to use a XIB file or not. The only reason to use a XIB file is if you have a complicated user interface that needs to be set up via Interface Builder. Otherwise, if you have a single view like a UITableView, UIImageView or UIView, simply implement loadView.

Now, regardless of how you are creating the interface, all UIViewController subclasses (save the standard ones, like UITableViewController, UINavigationController and UITabBarController) should be instantiated in code and sent an init message.
`

ViewControllerSubclass *vcs = [[ViewControllerSubclass alloc] init];

`

You are probably wondering, “Uhh… if I just send it init, how do I load its view from a XIB?” Good question. You will be overriding the init method of your UIViewController subclass (and overriding the superclasses designated initializer).
`

- (id)init
{
    return [super initWithNibName:@"ViewControllerSubclass" bundle:nil];
}
- (id)initWithNibName:(NSString *)name bundle:(NSBundle *)bundle
{
   return [self init];
}

`

How does this work out in code? Let’s pretend you have two UIViewController subclasses, RootViewController and DetailViewController. They are both intended to be part of a UINavigationController.

In your application delegate, you should be doing this:
`

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
    RootViewController *rootViewController = 
        [[RootViewController alloc] init];
    UINavigationController *navController = [[UINavigationController alloc] 
            initWithRootViewController:rootViewController];
    
    [window addSubview:[navController view]];
    [window makeKeyAndVisible];
}

`

In RootViewController’s implementation, you can do a little something like this:
`

- (void)viewDidLoad
{
    if(!detailViewController)
        detailViewController = [[DetailViewController alloc] init];
}

`

Therefore, none of your other classes need to know anything about any other UIViewController subclass. They just know if they want an instance of it – with a fully configured user interface – they can just send it init.

Easy enough.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project