On iOS 7, pushing a controller with a toolbar leav

2019-01-30 11:00发布

In my iOS app, my window's rootViewController is a tab bar controller with the a hierarchy like this:

  • UITabBarController
    • UINavigationController 1
      • FirstContentController
    • UINavigationController 2
      • ...
    • UINavigationController 3
      • ...
    • ...

When the user taps a certain row on FirstContentController, an instance of SecondController will be pushed onto its navigation controller. SecondContentController sets hidesBottomBarWhenPushed to YES in its init method and sets self.navigationController.toolbarHidden to NO in viewWillAppear:.

In iOS 6, the user would tap the row in FirstController and SecondController would get pushed onto the nav controller. Because it has hidesBottomBarWhenPushed set, it would hide the tab bar and, by the time the transition animation was complete, SecondController would be on the screen with its toolbar visible.

However, when testing this under iOS 7, hidesBottomBarWhenPushed's behavior seems to have changed. What I see now is:

  • the tab bar hides, as expected
  • the toolbar appears, as expected
  • a gap of unusable space exactly 49 pixels tall (the height of the tab bar) appears between the toolbar and the content view

The gap is completely unusable - it doesn't respond to touches and if i set clipsToBounds to YES on the main view, nothing draws there. After a lot of debugging and examining subview hierarchies, it looks like iOS's autosizing mechanism resizes the view controller's view to a height of 411 (on the iPhone 5). It should be 460 to reach all the way down to the toolbar, but the layout system seems to be including a "ghost" 49-pixel-tall tab bar.

This problem only occurs if the view controller has a tab bar controller as one if its parent containers.

On iOS 7, how can I have the tab bar disappear and a toolbar seamlessly slide into place when a new controller is pushed, and still have the view take up the entire space between the navigation item and the toolbar?

UPDATE

After further investigation, this only happens if SecondController's edgesForExtendedLayout is set to UIRectEdgeNone. However, unless I set that property to UIRectEdgeNone, the view's frame is too long and extends under the toolbar, where it can't be seen or interacted with.

14条回答
趁早两清
2楼-- · 2019-01-30 11:37

I built a new project using your Gist, and I encased the UITabBarController in a UINavigationController:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

    UITabBarController* tabController = [[UITabBarController alloc] init];

    tabController.viewControllers = @[
                                      [[UINavigationController alloc] initWithRootViewController:[[FirstViewController alloc] init]],
                                      [[UINavigationController alloc] initWithRootViewController:[[FirstViewController alloc] init]]
                                      ];

    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:tabController];
    [navController setNavigationBarHidden:YES];
    self.window.rootViewController = navController;

    return YES;
}

And to show the SecondViewController, here is what I did:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    SecondViewController* controller = [[SecondViewController alloc] init];

    // Reaching the UITabBarViewController's parent navigationController
    [self.parentViewController.navigationController pushViewController:controller animated:YES];
}

Finally, in the secondViewController:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    self.view.backgroundColor = [UIColor redColor];
    self.view.clipsToBounds = YES;

    // The following line only works in iOS7
    if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) {
        self.edgesForExtendedLayout = UIRectEdgeNone;
    }

    [self.navigationItem setRightBarButtonItem:[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:nil action:nil]];

    UIBarButtonItem * logoutButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemReply target:nil action:nil];
    NSMutableArray * arr = [NSMutableArray arrayWithObjects:logoutButton, nil];
    [self setToolbarItems:arr animated:YES];


    [self.navigationController setNavigationBarHidden:NO animated:YES];
    [self.navigationController setToolbarHidden:NO animated:YES];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [self.navigationController setNavigationBarHidden:YES animated:YES];
    [self.navigationController setToolbarHidden:YES animated:YES];
}

Here's what it does look: 3rd tentative

EDIT: Changed the example and changed the screenshot. Made the example iOS6 compatible.

查看更多
SAY GOODBYE
3楼-- · 2019-01-30 11:38

Uncheck "Hide bottoms bars on push" and set your autoconstraints as if there is a tab bar. Then in "ViewDidLoad" of the controller you want to hide the system tab bar, put the following code.

[self.tabBarController.tabBar setFrame:CGRectZero];

This makes sure the tab bar still accepts user interaction yet not visible to users. (other alternatives such as setting it 0 alpha or hidden will render tab bar useless) Now the autoconstaraints will make sure your view displays correctly with the tab bar height as zero.

查看更多
ら.Afraid
4楼-- · 2019-01-30 11:38

You mention that you can fix this by not touching the edgesForExtendedLayout. Is there a necessary reason that the content/controls of the view controller are contained in the root view of the pushed view controller? You might consider wrapping everything in a view that is the first and only child of the main view. Then adjust that view's frame in the viewDidLayoutSubviews of the pushed view controller to avoid having content permanently beneath the toolbar using the top/bottomLayoutGuide of the view controller.

查看更多
小情绪 Triste *
5楼-- · 2019-01-30 11:38

I think you can set SecondController's edgesForExtendedLayout to UIRectEdgeBottom.

查看更多
何必那么认真
6楼-- · 2019-01-30 11:48

It's a bug in iOS 7 UIKit due to this particular combination of:

  • UITabBarController
  • hidesBottomBarWhenPushed = YES
  • edgesForExtendedLayout = UIRectEdgeNone
  • UINavigationController toolbar

You should file a bug with Apple and include your sample code.

To work around the bug you need to remove one of those four conditions. Two likely options:

  1. Fix the layout of your "second" view controller so that it works correctly when edgesForExtendedLayout is set to UIRectEdgeAll. This could be as simple as setting the contentInset on a scroll view.

  2. Don't use UINavigationController's built-in toolbar. Instead, create a separate UIToolBar instance and manually add it to your second view controller's view.

查看更多
Deceive 欺骗
7楼-- · 2019-01-30 11:50

The key to this conundrum is that the navigationcontroller.view.frame size doesn't change. Going of batkin's Gist here is a gist of my own.

FirstViewController.m

#import "FirstController.h"
#import "SecondController.h"

@implementation FirstController

-(id)init
{
    if( (self = [super init]) )
    {
        self.tabBarItem.title = @"Foo";
        self.tabBarItem.image = [UIImage imageNamed:@"Tab Icon.png"];
    }

    return self;
}

-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
    return 1;
}

-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
    UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];

    cell.textLabel.text = @"Click";

    return cell;
}

-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
    SecondController* controller = [[SecondController alloc] init];
    self.tabBarController.tabBar.hidden = YES;
    [self.navigationController pushViewController:controller animated:YES];
}

@end

SecondViewController.m

#import "SecondController.h"

@implementation SecondController

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    self.view.backgroundColor = [UIColor redColor];
    self.view.clipsToBounds = YES;

    /* ENTER VORTEX OF DESPAIR */

    // without this, there's no gap, but the view continues under the tool 
    // bar; with it, I get the 49-pixel gap thats making my life miserable

    self.edgesForExtendedLayout = UIRectEdgeNone;

    //this resizes the navigation controller to fill the void left by the tab bar.
    CGRect newFrame = self.navigationController.view.frame;
    newFrame.size.height = newFrame.size.height + 49;
    self.navigationController.view.frame = newFrame;

    /* EXIT VORTEX OF DESPAIR */

    self.navigationController.toolbarItems = @[
                                               [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:nil action:nil]
                                               ];


}

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    self.navigationController.toolbarHidden = NO;

    // will log a height of 411, instead of the desired 460
    NSLog(@"frame: %@", NSStringFromCGRect(self.view.frame));
    NSLog(@"frame: %@", NSStringFromCGRect(self.navigationController.view.frame));
}

-(void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    self.tabBarController.tabBar.hidden = NO;
    self.navigationController.toolbarHidden = YES;

    //this resizes the navigation controller back to normal.
    CGRect newFrame = self.navigationController.view.frame;
    newFrame.size.height = newFrame.size.height - 49;
    self.navigationController.view.frame = newFrame;

    //this is optional and resizes the view to fill the void left by the missing toolbar.
    CGRect newViewFrame = self.view.frame;
    newViewFrame.size.height = newViewFrame.size.height + 49;
    self.view.frame = newViewFrame;

}

@end
查看更多
登录 后发表回答