4

Because I have some requirements for a tabbar that the normal iphone tabbar cannot provide, I am needing to build my own.

What is the best way to build my own tabbar, specifically, how to add/remove (show/hide) views in my main view controller in the right way, taking into account memory and best practices for subviews?

Nic Hubbard
  • 41,587
  • 63
  • 251
  • 412

3 Answers3

7

As I've stated elsewhere, it's almost never a good idea to get rid of the core navigational classes that are provided by UIKit. What type of application requirements do you have that you think merit a completely custom tab bar class? It's almost always possible to achieve the necessary customizations by either subclassing, categorizing, or making use of layers.

UPDATE 1: So here's what I did in some of my apps to get a custom tab bar implementation.

  1. Create a subclass of UITabBar
  2. Add a method to your custom subclass called something like -updateTabBarImageForViewControllerIndex:
  3. In Interface Builder, change the class of your tab bar controller's tab bar to your custom subclass
  4. In whatever class conforms to your tab bar controller's delegate (e.g., your app delegate), implement -tabBarController:shouldSelectViewController: and call -updateTabBarImageForViewControllerIndex: on your custom tab bar subclass

Basically, you want to notify your tab bar subclass every time the tab bar controller is about to switch view controllers. When this happens, determine what image you need to choose for your tab bar. You should have n images for your tab bar, one for the selected state of each tab. It's possible to actually fudge the implementation of UITabBarItem and just work with individual images, but it's a little more work.

// MyAppDelegate.m

- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
    // Determine the index based on the selected view controller

    NSUInteger viewControllerIndex = ...;

    [(MyTabBar *)tabBarController.tabBar updateTabBarImageForViewControllerIndex:viewControllerIndex];

    return YES;
}

// MyTabBar.m

- (void)updateTabBarImageForViewControllerIndex:(NSUInteger)index
{
    // Determine the image name based on the selected view controller index

    self.selectedTabBarImage = [UIImage imageNamed:...];

    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect
{
    CGContextDrawImage(UIGraphicsGetCurrentContext(), rect, self.selectedTabBarImage.CGImage);
}

UPDATE 2: Now that I think about it more, you actually could (and should) get away with what you're trying to achieve without subclassing UITabBar at all. Import <QuartzCore/QuartzCore.h> and take advantage of layer contents. :)

// MyAppDelegate.m

- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
    // Determine the image name based on the selected view controller

    CGImageRef newTabBarImageRef = [[UIImage imageNamed:...] CGImage];
    tabBarController.tabBar.layer.contents = (id)newTabBarImageRef;

    return YES;
}
CIFilter
  • 8,647
  • 4
  • 46
  • 66
  • Actually, with regards to UITabBar[Controller], Apple really don't like you subclassing it. It can be done, but you often fail a bunch of assert()s they've got in there to stop you doing it. When I asked about it on the dev forums, the answer was "Roll your own". (I didn't, as it happened, but there was pain and suffering along the way.) – Amy Worrall Feb 06 '11 at 08:38
  • There's nothing wrong with subclassing if all you're going to be doing is customizing the style by overriding `-drawRect:`, for example. Yes, you don't want to be overriding any methods that change the underlying logic, but there's no harm in subclassing if you're doing it appropriately. In any case, that's why I recommend just using layers if you can. No subclasses or categorizing required. – CIFilter Feb 06 '11 at 08:43
  • Lucas. I need to change the overlay color and the text color. From what I have seen, doing this might get my app rejected. – Nic Hubbard Feb 06 '11 at 22:53
  • I've done this in the past, and my app wasn't rejected. Granted, it was a while ago, but all I did was subclass the appropriate components and mess with the `-drawRect:` method to achieve the effect I wanted. All the rest of the heavy lifting fell through to the base classes. – CIFilter Feb 08 '11 at 01:46
  • And messing with -drawRect allowed you to customize the overlay color and text color of the tabbaritems? – Nic Hubbard Feb 09 '11 at 18:58
  • Within `-drawRect:` you can use any Core Graphics drawing routine to draw lines, shapes, gradients, text, and images. The simplest way is to just use images for each tab bar item to completely cover up the standard tab bar. See the screenshots for the Tim McGraw app I created: http://itunes.apple.com/us/app/tim-mcgraw/id341650507?mt=8 Is this what you're trying to do with the tab bar? – CIFilter Feb 09 '11 at 20:47
  • @Lucas - YES! That is exactly the kind of thing I am trying to accomplish. Would you be willing to share some of your code? – Nic Hubbard Feb 09 '11 at 23:43
  • Yes, I'll edit my answer with some code to accomplish this effect as soon as I can. – CIFilter Feb 09 '11 at 23:50
  • Thanks so much Lucas. I really appreciate it. – Nic Hubbard Feb 10 '11 at 01:19
  • Hey Lucas, just wondering if you had a chance to throw up that code sample. Thanks so much. – Nic Hubbard Feb 11 '11 at 23:50
  • Hey there. Sorry, I've been away from home where all my old project files are. The gist of it is that you subclass `UITabBar` and override `-drawRect:` to draw a background image. Then subclass `UITabBarItem` and either override `-initWithTitle:image:tag:` to draw a completely custom image or create a new method that takes a `UIImage` and draws it as the tab bar item's contents. I'll get back to you when I have access to my old projects. Or take a look at this: http://blog.theanalogguy.be/2010/10/06/custom-colored-uitabbar-icons/ – CIFilter Feb 14 '11 at 18:55
  • Thanks. Are you using a UITabBarController? I am, and was not able to get my subclassed UITabBar used, since Apple says you are not suppose to subclass UITabBarController. Are you only using a UITabBar with no controller? – Nic Hubbard Feb 15 '11 at 03:48
0

It really depends on your application. If you can afford to keep in memory all view controllers, that assigned to your tabbar, then you can use a simple array, that would store all appropriate view controllers, and you show them using the index in that array. It is also a good idea to create your custom view controller, that would store it's own tabbar image (and/or title). And your tabbar would take all that values from there.

If you can't afford so much memory (but it's not very possible) then you can store NSDictionary in the array, and not view controllers. And when user taps on the tabbar item you just unload the previous view controller and create the new one, with parameters from that dictionary. Or instead of the dictionary you can use some custom container class.

Max
  • 16,679
  • 4
  • 44
  • 57
  • Does the normal UITabBar load all views into memory? – Nic Hubbard Feb 06 '11 at 06:42
  • 1
    Yep. From docs - (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated So you first have to create view controllers. – Max Feb 06 '11 at 06:44
  • You're really doing 95% of the work already accomplished by `UIKit` by making your own tab bar (controller). Why not just do 5% of the work with some simple subclassing or layer content manipulation? – CIFilter Mar 02 '11 at 01:49
0

This two accepted answers break in iOS5. What I did instead was to keep the custom tab bar subclass, and leave only this method intact:

- (void)drawRect:(CGRect)rect
{
   CGContextDrawImage(UIGraphicsGetCurrentContext(), rect, nil);
} 

Then in MainWindow.xib, I created a custom view with an imageview as the background and UIButtons as the tab bar items.

In the TabController delegate, I update the buttons' selected states.

- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
 // Determine the index based on the selected view controller

 UIButton *newBtn;

 if(viewController == homeVC) {
   ...
 }
 // Update the buttons
 newBtn.selected = YES;
 _selectedTabBtn.selected = NO;
 _selectedTabBtn = newBtn;

 return YES;
}
oohaba
  • 592
  • 5
  • 17
  • In iOS5 they allow you to customize the tabbar, so this isn't a problem anymore. – Nic Hubbard Oct 14 '11 at 00:05
  • Do you have a link to the new documentation? Couldn't find anything in UITabBarItem, UITabBar, or UITabBarController. – oohaba Oct 14 '11 at 04:43
  • 2
    There are new backgroundImage, selectedImageTintColor, selectionIndicatorImage and tintColor properties. Look in the XCode 4.2 docs, you will then see it in the iOS 5 library. – Nic Hubbard Oct 14 '11 at 05:59
  • Ah, I see it now. That works, but my solution might still be useful for those who want to be backwards-compatible with iOS4. – oohaba Oct 14 '11 at 17:00