3

I've created a UITabBarController with multiple UIViewControllers to act as its tabs. I would also like to display a constant UINavigationBar within this UITabBarController that will persist across tabs. Since none of the child view controllers for the UITabBarController are UINavigationControllers, I'm hoping this is possible and won't cause a conflict. Is there a way to do this?

Currently I am copy-pasting the exact same UINavigationBar and corresponding logic to each child view controller, so I'm guessing that there's a better way.

In terms of why this would be useful: Imagine a UINavigationBar that contains two UIBarButtonItems for "Logout" and "Account." Pressing "Logout" would log you out and push a modal view controller so that you can log back in, while pressing "Account" would bring up a modal view controller with your account information.

I found this existing question on Stack Overflow, but both the question itself and its comments were very poorly worded and uninformative: How to add a navigation bar to UITabBarController?


UPDATE:

I have a feeling that I might not be explaining myself well enough so I created a graphic to illustrate what I am trying to do. In the image below, I have a UITabBarController that is presented modally with four UIViewControllers as children. I'd like there to be a UINavigationBar that is constant so that the user can press Done to dismiss the modally presented view controller. Since each child view controller is a single view, it seems unnecessary to embed each one in its own UINavigationController for the sake of getting the UINavigationBar to appear. Currently when the UITabBarController is presented modally no navigation bar appears.

Visualization of desired UITabBarController with UINavigationBar and child UIViewControllers

Community
  • 1
  • 1
Ken M. Haggerty
  • 24,902
  • 5
  • 28
  • 37
  • What is the code you are copy/pasting? I've set this up both using a storyboard and in code, no copy/pasting was needed. – Jeffery Thomas Jan 30 '14 at 03:28
  • Currently I'm creating an IBAction in each child view controller and delegating it back to the UITabBarController. This isn't as wasteful/repetitive as also copying the main logic itself, but it still seems like it should be DRY-er. – Ken M. Haggerty Jan 30 '14 at 14:18
  • OK, I had to re-read it. You want a single navigation controller for all tabs. What happens when you press a tab icon? Does it just push a new view controller onto the navigation controller or does it replace the top view controller? – Jeffery Thomas Jan 30 '14 at 14:34
  • I don't want a single `UINavigationController` for all tabs, just a single `UINavigationBar`. Currently if I add a single `UINavigationBar` to the `UITabBarController` in Interface Builder, it does not appear at all. However, I suppose I could have each tab connect to the same `UINavigationController` and overwrite `-[UITabBarController tabBarController:shouldSelectViewController:]` method so that it simply updates the `UINavigationController`'s child view instead of replacing it altogether. – Ken M. Haggerty Jan 30 '14 at 14:39
  • This can be done in Interface Builder. The first project I worked did this. If it's a storyboard, it's dead simple, If it's a NIB, then it's a little trickier, but still doable. – Jeffery Thomas Jan 30 '14 at 18:08
  • I'll keep trying myself, but could you post how you did it as an answer below? Or did it just work for you when you dragged-and-dropped the `UINavigationBar` into the `UITabBarController` in Interface Builder? – Ken M. Haggerty Jan 30 '14 at 23:56

3 Answers3

3

I finally got this to work!!

In short: You can't inherit or persist a UINavigationBar, but you can set its UIBarButtonItems via its UINavigationItem.

Here is what I did, followed by relevant code, followed by caveats:

What I Did:

1) Subclass UITabBarController: I created my own custom subclass of UITabBarController. Since I want my two UIBarButtonItems to persist across tabs, I also created @propertys in my UITabBarController subclass. Since I also want my UITabBarController to handle these UIBarButtonItems, I also created one method for each button within my UITabBarController subclass.

2) Embed View Controllers For Tabs In UINavigationControllers: This is the best way to get the desired effect even if you don't want or need a UINavigationController because in iOS 7 the navigation bar extends upwards underneath the status bar to the top of the screen. (As far as I was able explore, I was unable to replicate this effect in iOS 7 using a UINavigationBar without a UINavigationController.)

3) Assign UIBarButtonItems To Top View Controllers: I assigned the UIBarButtonItems from my UITabBarController subclass to my desired view controllers from within my tab bar controller's -[initWithNibName:bundle:] and -[awakeFromNib] methods. I did this by going through all of the view controllers assigned as tabs in my tab bar controller via self.viewControllers; checking to see if they are UINavigationControllers; and assigning my UIBarButtonItems to my navigation controllers' top view controllers' navigation items via [navigationController.topViewController.navigationItem setLeftBarButtonItem:self.leftBarButton] and [navigationController.topViewController.navigationItem setRightBarButtonItem:self.RightBarButton].

Relevant Code:

// MyTabBarController.h //

@interface MyTabBarController () <UITabBarControllerDelegate>
@property (nonatomic, strong) UIBarButtonItem *leftBarButton;
@property (nonatomic, strong) UIBarButtonItem *rightBarButton;
- (void)setup;
- (void)buttonLeft:(UIBarButtonItem *)sender;
- (void)buttonRight:(UIBarButtonItem *)sender;
@end

@implementation MyTabBarController

@synthesize leftBarButton = _leftBarButton;
@synthesize rightBarButton = _rightBarButton;

- (UIBarButtonItem *)leftBarButton
{
    if (!_leftBarButton) _leftBarButton = [[UIBarButtonItem alloc] initWithTitle:@"Left" style:UIBarButtonItemStyleBordered target:self action:@selector(buttonLeft:)];
    return _leftBarButton;
}

- (UIBarButtonItem *)rightBarButton
{
    if (!_rightBarButton) _rightBarButton = [[UIBarButtonItem alloc] initWithTitle:@"Right" style:UIBarButtonItemStyleBordered target:self action:@selector(buttonRight:)];
    return _rightBarButton;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self)
    {
        [self setup];
    }
    return self;
}

- (void)awakeFromNib
{
    [self setup];
}

- (void)setup
{
    [self setDelegate:self]; // since I am using myself as the UITabBarControllerDelegate
    for (UIViewController *viewController in self.viewControllers)
    {
        if ([viewController isKindOfClass:[UINavigationController class]])
        {
            UINavigationController *navigationController = (UINavigationController *)viewController;
            [navigationController.topViewController.navigationItem setLeftBarButtonItem:self.leftBarButton];
            [navigationController.topViewController.navigationItem setRightBarButtonItem:self.rightBarButton];
        }
    }
}

- (void)buttonLeft:(UIBarButtonItem *)sender
{
    // Left bar button action
}

- (void)buttonRight:(UIBarButtonItem *)sender
{
    // Right bar button action
}

@end

Caveats:

In the process of reaching my solution I learned a lot, including the following:

  • You can't set your own UINavigationBar programmatically. At best, you can create your own custom UINavigationBar subclass and set it to your UINavigationController via Interface Builder / Storyboard as described here (external website: iOS Dev Notes).
  • You can't create and use a UINavigationBar from an XIB, as described here: How to customize UINavigationBar with XIB?
  • Hacking together a combined "UITabBarController/UINavigationController" by dragging in a UINavigationBar and UITabBar into a UIViewController doesn't work in iOS 7, since iOS 7 uses overlapping translucent UI elements whose overlaps can't be set well programmatically (e.g., the extension of the navigation bar behind the status bar).
  • Embedding a UITabBarController in a UINavigationController works but is explicitly discouraged / forbidden by Apple's documentation. This is because it's bad logic to embed a top-level navigation feature (i.e., a UITabBarController that allows the user to switch between parallel interfaces) in a UI that's supposed to be able to push and pop its view controllers.
Community
  • 1
  • 1
Ken M. Haggerty
  • 24,902
  • 5
  • 28
  • 37
  • SO my que - when u will traverse thru tabs of tab bar, each time - (void)setup will get called?? if yes, then u r doing the same thing for logout or account each time. that is nothing but the same u asked in ur que. I mean tho these buttons are feeling like generic for all VC's, in back end u r calling the same function again and again when u traverse thru tabs!! – Nayan Feb 04 '14 at 05:06
  • I'm having a hard time figuring out what your asking, so if I don't answer your question please clarify. I set my desired `UIBarButtonItem`s via my `UITabBarController` and set the target for my `UIBarButtonItem`s to `self` (i.e., my UITabBarController). That way, all of my logic for my `UIBarButtonItem`s is in my `UITabBarController`, no copy-pasting required! – Ken M. Haggerty Feb 04 '14 at 14:17
0

OK, This is kind of old school, but here is what I did.

First, I created a Nib to hold the tab bar controller. I did this by creating an empty Nib and dragging a tab bar controller into it as the first item. The tab bar controller is pre-populated with two view controllers. These can be deleted.

Next, Drag into the tab bar controller all the navigation controllers you need. You should be careful that they are children of the tab bar controller, not siblings of the tab bar controller.

The navigation controller is pre-populated with a view controller. For each view controller, select the view controller. In the Attributes inspector for the set the NIB name to the NIB for your view controllers (or you could set the Custom Class in the Identity inspector).

Finally, load the tab bar controller in -application:didFinishLaunchingWithOptions:.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSArray *views = [[NSBundle mainBundle] loadNibNamed:@"JLTRootController" owner:nil options:nil];
    UIViewController *controller = views[0];

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    self.window.backgroundColor = [UIColor whiteColor];
    self.window.rootViewController = controller;
    [self.window makeKeyAndVisible];
    return YES;
}

Hope that helps

Jeffery Thomas
  • 42,202
  • 8
  • 92
  • 117
  • It doesn't, but I must be wording my question incorrectly. I'm looking to have the *same* `UINavigationBar` in all of the child view controllers of the `UITabBarController`. From what I was able to achieve in following your code, I just have multiple distinct `UINavigationController`s, each with their own unique `UINavigationBar`. – Ken M. Haggerty Jan 31 '14 at 14:26
0

I know this thread was started and answered a while ago, but I figured I'd contribute a gem:

  • Add a UINavigationController to your storyboard before the UITabBarController. Control-drag to set the UITabBarController as its root view controller. This UINavigationController will serve as the persistent navigation bar across the top.
  • For each tab, set a UINavigationController as the tabbar's view controller, and link the existing view controller as its root view controller.
  • For each tab, go in the settings for the UINavigationController and uncheck the box that says 'Shows Navigation Bar'. This will hide the inner navigation bars.

You now have a persistent bar across the top (controlled by the UINavigationController before the UITabBarController), without writing any code :) all in storyboards. You can even drag buttons onto it as well as a custom title view.

If you want to access this programmatically:

  • You can subclass the UITabBarController. Then self.navigationItem.rightBarButton should allow you to access and set the buttons easily.
  • You can also set it directly from the tab view controllers using `self.tabbarcontroller.navigationitem.rightBarButton'. So it's a slight level of indirection.

Simple! This answer is based on one of my previous answers, which you can check out here if you like more detail and pictures: how iphone facebook app make the navigation bar fixed

Community
  • 1
  • 1
Thomas Verbeek
  • 2,361
  • 28
  • 30