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
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.
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 0x7fcc6d38b860Blue: 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.
Any suggestion?
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.
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.
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.
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With