11
votes

I'm trying to find a workaround to this situation:
I have a UITabBarController one of its segue is connected to a container view controller (BannerViewController) that I'm using to embed a UINavigationController, the navigation controller pushes other container view controllers (EventListContainerviewController) each of this contains a tableview controller.
Here a screen from my story board enter image description here
The problem is that the last container view is displayed in a smaller frame than their containers. It seems that they loose the tabbar space at the bottom. enter image description here
I've forced all view controllers, navigation controller and tabbar controller to avoid do not extent their edges and adjust insets.

The color means:

Red: main view of the BannerContainerViewController 0x7fcc6d38bb00
light green : the container view of the BannerContainerViewController 0x7fcc6d38b860

Blue: main view of the EventListContainerViewController 0x7fcc6bd7b7c0
Orange: the container view of the EventListContainerViewController 0x7fcc6bd7b690

It seems that something changes when the navigation controller add the blue container view reducing its size by the same amount of a tabbar (49pt). It can be also seen in the recursive description of the view hierarchy:

<UIWindow: 0x7fcc6bd5af40; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x7fcc6bd4dd80>; layer = <UIWindowLayer: 0x7fcc6bd317c0>>
   | <UILayoutContainerView: 0x7fcc6bd671c0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7fcc6bd66de0>>
   |    | <UITransitionView: 0x7fcc6bd6a980; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x7fcc6bd6ace0>>
   |    |    | <UIViewControllerWrapperView: 0x7fcc6d3a7b20; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7fcc6d3a7db0>>
   |    |    |    | <UIView: 0x7fcc6d38bb00; frame = (0 0; 320 519); autoresize = W+H; layer = <CALayer: 0x7fcc6d38bbd0>>
   |    |    |    |    | <UIView: 0x7fcc6d38b860; frame = (0 0; 320 519); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7fcc6d38b930>>
   |    |    |    |    |    | <UILayoutContainerView: 0x7fcc6d398000; frame = (0 0; 320 519); autoresize = W+H; gestureRecognizers = <NSArray: 0x7fcc6d3a4b50>; layer = <CALayer: 0x7fcc6d387670>>
   |    |    |    |    |    |    | <UINavigationTransitionView: 0x7fcc6d39cfa0; frame = (0 0; 320 519); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x7fcc6d39cc40>>
   |    |    |    |    |    |    |    | <UIViewControllerWrapperView: 0x7fcc6bf31230; frame = (0 0; 320 519); autoresize = W+H; layer = <CALayer: 0x7fcc6bf31300>>
   |    |    |    |    |    |    |    |    | <UIView: 0x7fcc6bd7b7c0; frame = (0 64; 320 406); autoresize = W+H; layer = <CALayer: 0x7fcc6bd7afe0>>
   |    |    |    |    |    |    |    |    |    | <UIView: 0x7fcc6bd7b690; frame = (0 0; 320 406); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7fcc6bd7aac0>>
   |    |    |    |    |    |    |    |    |    |    | <UITableView: 0x7fcc6f03c400; frame = (0 0; 320 406); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x7fcc6bf34aa0>; layer = <CALayer: 0x7fcc6bf30bc0>; contentOffset: {0, 0}; contentSize: {320, 170}>
   |    |    |    |    |    |    |    |    |    |    |    | <UITableViewWrapperView: 0x7fcc6bf35960; frame = (0 0; 320 406); gestureRecognizers = <NSArray: 0x7fcc6bf363e0>; layer = <CALayer: 0x7fcc6bf35ed0>; contentOffset: {0, 0}; contentSize: {320, 406}>
   |    |    |    |    |    |    |    |    |    |    |    |    | <EventCell: 0x7fcc6bca2950; baseClass = UITableViewCell; frame = (0 85; 320 85); autoresize = W; layer = <CALayer: 0x7fcc6bca2ca0>>

I've tries to use custom segue instead of container view try to forcing the layout, but I guess is a navigation controller fault.
As you can see the banner is not overlapping the view as intended.
enter image description here

Any suggestion?

3
Did you check that your viewcontroller has the Extend Edges UnderBottomBars checked ?las
sure as I've written in the questionAndrea
ops... they all have Extended Edges uncheckedAndrea
@las it seems to has fixed the issue. If I set the BannerViewController to extend underbottom bars, frames are correct, but I don't understand why, it seems a trick more than solutionAndrea
I can't believe that someone else had this specific problem too, I was having such a hard time with this, really glad I found this post and saw that it was a known issuebeno

3 Answers

16
votes

This issue still exists in iOS 10. A UINavigationController embedded in a container view, which in turn is contained in a UITabBarController, will lay out all views in the navigation stack with extra space for a "phantom" tab bar at the bottom.

The easiest solution is to subclass UINavigationController and return 'nil' from tabBarController.

class MyNavigationController: UINavigationController {

    override var tabBarController: UITabBarController? {
        return nil
    }

}

This makes the views in the navigation stack think they don't have a tab bar controller, so they won't leave extra space for it during layout. I haven't noticed any negative side effects from this fix, but obviously the views in this navigation controller's stack will no longer have access to the tab bar controller. If that's a problem, you can use a more generic method to find the tab bar controller (or any "parent" view controller).

For example, if the main view controller for your app is a UITabBarController named "MainViewController", you can extend UIViewController with a convenience method to find it.

extension UIViewController {

    func mainViewController() -> MainViewController? {
        var vc: UIViewController? = self
        while !(vc is MainViewController) && vc != nil {
            vc = vc?.parent ?? vc?.navigationController ?? vc?.presentingViewController
        }
        return vc as? MainViewController
    }

}

This works because a tab bar controller is the parent of its direct child view controllers. The above method works its way up the chain of parent, presenting and nav controllers to eventually reach a child of the tab bar controller, which returns the tab bar controller as its parent.

7
votes

Turns out that is a UIKit bug, I've filed a radar 19996374. Here is the explanation:

When a navigation controller is placed inside of a tab bar controller, responsibility for laying out the content (your) view controller is given fully to the navigation controller. The tab bar controller sizes the navigation controller's view to match the tab bar controller's view bounds. In turn, the navigation controller accounts for the tab bar height when laying out the content view controller. Things break when you inject a view controller between the tab bar controller and the navigation controller. The tab bar controller sees that the selectedViewController is not a UINavigationController and applies the normal layout rules. But contained navigation controller sees that its tabBarController property contains a valid UITabBarController instance and assumes that it is still responsible for handling the tab bar height when laying out the content (your) view controller. The result is that the content view controller is inset by the tab bar height twice, as you have observed

As a workaround

You can work around this problem by setting the isTranslucent property of the tab bar to YES. Then, in AFBannerViewController, override -edgesForExtendedLayout to return UIRectEdgeAll. The AFBannerViewController will now underlap the tab bar (so set the background color to something other than pink) but the navigation controller will apply the proper inset to the content view controller.

2
votes

Did encounter the exact same problem, and found your post.

Actually, I managed to fix it using another method.

My top, leading and trailing constraints for the container view are bounds to the safe area, BUT, the bottom one is set to the superview (See attached screenshot)

The trick was to play with the Extend Edges configuration, and set "Under Opaque bars" to true, and voila.

I hope it will help other people.

Regards

enter image description here

enter image description here