Search

Good iPhone Practices

Joe Conway

3 min read

Mar 17, 2009

iOS

Good iPhone Practices

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.

Zack Simon

Reviewer Big Nerd Ranch

Zack is an Experience Director on the Big Nerd Ranch design team and has worked on products for companies ranging from startups to Fortune 100s. Zack is passionate about customer experience strategy, helping designers grow in their career, and sharpening consulting and delivery practices.

Speak with a Nerd

Schedule a call today! Our team of Nerds are ready to help

Let's Talk

Related Posts

We are ready to discuss your needs.

Not applicable? Click here to schedule a call.

Stay in Touch WITH Big Nerd Ranch News