Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

It is a little thing about UIViewController, but it has always bothered me - the boilerplate code needed to setup some of a view controller's default properties (e.g. the tab bar item and navigation item) and the location of this code.

Apple's documentation says the following:

tabBarItem

The tab bar item that represents the view controller when added to a tab bar controller.

@property(nonatomic, retain) UITabBarItem *tabBarItem

Discussion

This is a unique instance of UITabBarItem created to represent the view controller when it is a child of a tab bar controller. The first time the property is accessed, the UITabBarItem is created. Therefore, you shouldn’t access this property if you are not using a tab bar controller to display the view controller. To ensure the tab bar item is configured, you can either override this property and add code to create the bar button items when first accessed or create the items in your view controller’s initialization code.

The default value is a tab bar item that displays the view controller’s title.

The default implementation is helpful, but it's certainly not enough. So where does one setup the remaining properties of the tab bar item instance that is lazily provided by UIViewController?

I've seen (and done) just about every possibility, but the highlights:

1. Set the property from the "upstream" object.

If you build out a tab bar controller in code (say from your App Delegate), then it is a real temptation to setup the tab bar items. You're right there setting up the controller, and in your brain it is easy to group the tab bar item setup process as part of that. However, I fully endorse this approach is "wrong." The tab bar item is part of and managed by a view controller.

2. In the init method of your subclass.

This certainly seems logical: in order for your view controller to function you'll need a tab bar item, and so why not go ahead and set it up when you create the view controller? Well, because that defeats the lazy loading and couples your view controller with knowledge that it is part of a tab bar controller. What happens when the layout changes and that view controller actually gets pushed onto a navigation stack? Will someone remember to refactor the tab bar item code out of init? I also endorse this as "wrong" because it doesn't follow the intention of lazy loading the property. Apple's documentation suggests this is a viable option, but maybe they are implying...

3. In -[UIViewController viewDidLoad]

I'd wager this is the most common place to do the setup. I know I've written the following lines of code a couple dozen times:

if (self.tabBarController) {
    UITabBarItem *tbi = [self tabBarItem];
    [tbi setImage:[UIImage imageNamed:@"tab-bar-item-icon"]];
    [tbi setTitle:NSLocalizedString(self.title, @"Tab Bar Item - View Controller 1")];
    [tbi setBadgeValue:@""];
    [self setTabBarItem:tbi];
}

My problem with this approach is it doesn't really make sense why it needs to be in viewDidLoad. The property is already lazy loaded when a the tab bar controller asks it's contained view controllers for their tab bar items. This scatters the responsibility of creating and configuring the item across non-related methods. Which leads me to the final solution...

4. Override the lazy load accessor method

Apple suggests this in their documentation, and stylistically follows the design they chose. Okay, so, how does one accomplish such a feat? There are a couple of solutions I've come up with, and I'd like to know if there are any others (that may be more elegant). Both of these seem a little too clever, which sends of alarm bells inside my head.

Option 1

Back the property with an instance variable writable by your subclass.

@implementation ViewControllerA
{
    UITabBarItem *_viewControllerATabBarItem;
}

- (UITabBarItem *)tabBarItem
{
    if (!_viewControllerATabBarItem) {
        _viewControllerATabBarItem = [super tabBarItem];
        [_viewControllerATabBarItem setImage:[UIImage imageNamed:@"tab-bar-item-icon"]];
        [_viewControllerATabBarItem setTitle:NSLocalizedString(self.title, @"Tab Bar Item - View Controller 1")];
        [_viewControllerATabBarItem setBadgeValue:@""];
    }

    return _viewControllerATabBarItem;
}

- (void)setTabBarItem:(UITabBarItem *)tabBarItem
{
    if (tabBarItem != _viewControllerATabBarItem) {
        _viewControllerATabBarItem = tabBarItem;
    }
}

@end

The necessity to back the property with an instance variable in our subclass is due to the fact Apple have declared the _tabBarItem instance variable with @package. This approach is perfectly reasonable, however, it certainly doesn't reduce boilerplate! Also, it costs a mostly unused _tabBarItem instance variable's resources by calling super. You certainly could alloc and init a UITabBarItem, but this might make your code brittle with future changes to the UIViewController class.

Option 2

Use Key-Value Coding to access the instance variable directly

- (UITabBarItem *)tabBarItem
{
    if (![self valueForKey:@"_tabBarItem"]) {
        UITabBarItem *tbi = [super tabBarItem];
        [tbi setImage:[UIImage imageNamed:@"tab-bar-item-icon"]];
        [tbi setTitle:NSLocalizedString(self.title, @"Tab Bar Item - View Controller 1")];
        [tbi setBadgeValue:@""];

        [self setTabBarItem:tbi];
    }

    return [self valueForKey:@"_tabBarItem"];
}

This is less boilerplate and doesn't have a need for a second instance variable, but seems like clever shenanigans that will make future-me angry. It certainly works, but is there a more straight forward way?

I'm curious to get feedback on my thoughts and proposed solutions, and also see other implementations. It's such a small thing and seems silly to think about given how many possible solutions there are, but there doesn't seem to be a clear cut better-than-all-the-others solution. So, how do you handle such a mundane detail?

NOTE: I've left the discussion of XIBs and Storyboard out of possible solutions because Navigation Items and Tab Bar Items have some states you cannot influence from Interface Builder. E.g. tab bar item's setFinishedSelectedImage:withFinishedUnselectedImage: or navigation items's leftItemsSupplementBackButton. If your app doesn't need these, I fully endorse using Interface Builder to setup these properties!

share|improve this question

2 Answers 2

up vote 2 down vote accepted

It seems like the essential goals you're seeking in a solution are (1) proper encapsulation and (2) being in accord with the lazy-loading approach to setting up the tab bar item.

I think Option 4.2 is closest to how I would achieve those goals, but I wouldn't use KVC. Instead, I would capture the return value of [super tabBarItem], set the appropriate properties on every access, and return that instance. No extra ivar, no mucking with implementation details.

As I see it, the laziness only has value when the VC never winds up in a tab bar controller. If you know for certain that you will need the tab bar item (knowledge that you obtain when the accessor is called), who cares about a few redundant calls to set its properties? It's not like that method gets called more than a couple of times in the lifespan of the average VC.

share|improve this answer
    
Yes! An obvious answer, and a reasonable justification for the messy parts. I like it. –  edelaney05 Jan 21 '13 at 19:40

A subclass of a vc that otherwise could be used as a tab bar vc or a nav controller vc would probably achieve your goals

Additionally, one could make a category to tuck all the boilerplate in and just set some optional params in for that

In reality, I think few people care to actively make their VCs independent of being in tab bars/nav controllers. There are a host of other differences (e.g. Dismissal, etc) that make them different

share|improve this answer
    
are you suggesting an inheritance of UIViewController -> ContentViewController -> TabBarContentViewController? I completely agree with your closing statement, but that's why I'm a weirdo, right? :-) –  edelaney05 Jan 20 '13 at 18:42

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.