Show UITabBar when UIViewController pushed

11.7k views Asked by At

Here's my situation :
I have a UINavigationController inside a UITabBarController. When I drill down the navigation controller, at some point I have to hide the UITabBar because I want the view to have as much space as possible.
I do that by using self.hidesBottomBarWhenPushed = YES inside the pushed UIViewController, and it works quite well.
However, I want to show the UITabBar back in the following pushed controllers. I've tried to put self.hidesBottomBarWhenPushed = NO in the other controllers, but the UITabBar won't come back.

It seems to be normal as the documentation states :

hidesBottomBarWhenPushed

If YES, the bar at the bottom of the screen is hidden; otherwise, NO. If YES, the bottom bar remains hidden until the view controller is popped from the stack.

And indeed, when the controller with this property set to yes is popped, the tabbar does come back.

Is there any proper way to show the UITabBar when a controller is pushed, once it's been hidden?

Thanks in advance

3

There are 3 answers

1
MHC On BEST ANSWER

Okay, here have we an awfully long way to go.

As you read from the document, the default behavior is clear: once a view controller's hides... property is YES, the tab bar is hidden until the view controller is popped. What you want directly contradicts this, and for various reasons, I would first recommend not to take this approach.

However, it doesn't mean it is impossible to implement.


  • Setting hides... property back to NO

You cannot modify the default behavior. To show the tab bar, all view controllers in the stack must set their hides... property to NO. So from the view where the tab bar is hidden, if you want to show the bar again when a new view is pushed, you have to set the previous view controller's hides... property back to NO again.

Just before you push a new view controller, set the property back to NO.

// ... prepare viewControllerToPush

self.hidesBottomBarWhenPushed = NO;
[self.navigationController pushViewController:viewControllerToPush animated:YES];
[viewControllerToPush release];

By doing this, you will have the tab bar again. However, you will recognize the tab bar is pushed in from the left, while the new view is pushed from the right. This is clearly not desirable, so we need to fix this.


  • Overriding the layer action for the tab bar

The thing is, the default layer action (the animation) used when the tab bar appears again, is a push transition animation from the left. UITabBar implements - (id < CAAction >)actionForLayer:(CALayer *)layer forKey:(NSString *)key method that tells to use the animation from the left for the case. We need to override this method, to return an animation from the right instead.

To show the tab bar back, Cocoa modifies its layer's position property. Therefore, our new method should return an animation from the right for the key position, and for all the other keys, it should use the default implementation. Note that using position for the tab bar is not documented by Apple, so it's subject to change without a notice in the following versions. We are implementing something directly contradicts Apple's specification anyway, so can't complain much.

However, you cannot just use subclassing to override the method. Because even if you have a custom subclass of UITabBar, you cannot modify UITabBarController class to use it instead of the default UITabBar.

So, it gets a bit more complex. In order to implant your own logic to UITabBar class, you have to 'swap' the implementation for the message actionForLayer: forKey:.

First, add a new method to UITabBar class using category.

@interface UITabBar(customAction)
@end

@implementation UITabBar(customAction)

- (id < CAAction >)customActionForLayer:(CALayer *)layer forKey:(NSString *)key {
    if ([key isEqualToString:@"position"]) {
        CATransition *pushFromRight = [[CATransition alloc] init];
        pushFromRight.duration = 0.25; 
        pushFromRight.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; 
        pushFromRight.type = kCATransitionPush; 
        pushFromRight.subtype = kCATransitionFromRight;
        return [pushFromRight autorelease];
    } 
return [self defaultActionForLayer:layer forKey:key];
}
@end

And in the viewDidAppear method of the tab bar controller, insert the following codes.

Method original = class_getInstanceMethod([UITabBar class], @selector(actionForLayer:forKey:));
Method custom = class_getInstanceMethod([UITabBar class], @selector(customActionForLayer:forKey:));

class_replaceMethod([UITabBar class], @selector(actionForLayer:forKey:), method_getImplementation(custom), method_getTypeEncoding(custom));
class_addMethod([UITabBar class], @selector(defaultActionForLayer:forKey:), method_getImplementation(original), method_getTypeEncoding(original));

You want to do this in viewDidAppear rather than viewDidLoad, because otherwise the tab bar will slide in from the right in the first time the application shows up.

Now when a UITabBar instance gets a message actionsForLayer forKey:, the custom method customActionForLayer forKey: is invoked. It intercepts the key position, and returns an animation from the right. If it's for another key, it invokes the original implementation of the message, which is now connected to the message defaultActionsForLayer forKey:.


Okay, there we are. Remember when popping back the views, you may have to set the hides... property back to YES, because you set it to NO when pushing a new view (and do some similar tricks to animate it properly).

I've spent some time on this but ironically, I have to say *Do not use this again, because it uses an undocumented information of Cocoa classes ("position" key for tab bar animation), contradicts the documented behaviors, and is against Apple's human interface guideline. You may find out that your application wouldn't work the same with the following SDK versions, that the users cannot easily adopt the interface, or even that Apple rejects your application on App store.

Then what on earth is my answer for? Well, an example of some interesting topics on the iOS development, I guess (and a proof showing that I'm terribly unproductive today :P).

3
AmitP On

hidesBottomBarWhenPushed is not deprecated. I found it is very simple to achieve the hide and show UITabBar using the following method:

self.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
self.hidesBottomBarWhenPushed = NO;

So right after you push the detailViewConroller, you should reset the hide property back to NO. This way it will show up again when the detail view pops back. no need for any additional changes in viewWillApear/ disapear,etc.. Enjoy.

0
Michael Thiel On

Here's my approach to this when using Storyboards in iOS 5:

Set the hidesBottomBarWhenPushed property to NO before performing the push segue:

- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
   if( [segue.identifier isEqualToString:@"PushDetailView"] )
   {
      self.hidesBottomBarWhenPushed = NO;
   }

   [super prepareForSegue:segue sender:sender];
}

The segue identifier is obviously up to you to name.

Set it back to YES immediately when the view controller's view will disappear:

- (void)viewWillDisappear:(BOOL)animated
{
   self.hidesBottomBarWhenPushed = YES;

   [super viewWillDisappear:animated];
}

Works like a charm (tested on iOS 5.1) using all the right animations for the UITabBar.