Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UINavigationController embedded in a container view, displays a Table View Controller with wrong size

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?

like image 278
Andrea Avatar asked Feb 19 '15 14:02

Andrea


People also ask

Which method is used to display the view controller in the UINavigationController container in swift?

You add and remove view controllers from the stack using segues or using the methods of this class. The user can also remove the topmost view controller using the back button in the navigation bar or using a left-edge swipe gesture.

Is UINavigationController a UIViewController?

A UINavigationController does a lot of this tedious work for you. As mentioned, it contains a stack of UIViewControllers. It will create a navigation bar at the top that will allow you to easily go back up the hierarchy of view controllers.

How UINavigationController works?

A navigation controller is a container view that can manage the navigation of hierarchical contents. The navigation controller manages the current displaying screen using the navigation stack. Navigation stack can have “n” numbers of view controllers.


3 Answers

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.

like image 130
Ryan Holmes Avatar answered Oct 19 '22 16:10

Ryan Holmes


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

like image 34
Gougou Avatar answered Oct 19 '22 14:10

Gougou


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.

like image 7
Andrea Avatar answered Oct 19 '22 16:10

Andrea