Content falls beneath navigation bar when embedded in custom container view controller.

Your custom container view controller will need to adjust the contentInset of the second view controller according to your known navigation bar height, respecting the automaticallyAdjustsScrollViewInsets property of the child view controller. (You may also be interested in the topLayoutGuide property of your container - make sure it returns the right value during and after the view switch.)

UIKit is remarkably inconsistent (and buggy) in how it applies this logic; sometimes you'll see it perform this adjustment automatically for you by reaching multiple view controllers down in the hierarchy, but often after a custom container switch you'll need to do the work yourself.


This seems to all be a lot simpler than what people make out.

UINavigationController will set scrollview insets only while laying out subviews. addChildViewController: does not cause a layout, however, so after calling it, you just need to call setNeedsLayout on your navigationController. Here's what I do while switching views in a custom tab-like view:

[self addChildViewController:newcontroller];
[self.view insertSubview:newview atIndex:0];
[self.navigationController.view setNeedsLayout];

The last line will cause scrollview insets to be re-calculated for the new view controller's contents.


FYI in case anyone is having a similar problem: this issue can occur even without embedded view controllers. It appears that automaticallyAdjustsScrollViewInsets is only applied if your scrollview (or tableview/collectionview/webview) is the first view in their view controller's hierarchy.

I often add a UIImageView first in my hierarchy in order to have a background image. If you do this, you have to manually set the edge insets of the scrollview in viewDidLayoutSubviews:

- (void) viewDidLayoutSubviews {
    CGFloat top = self.topLayoutGuide.length;
    CGFloat bottom = self.bottomLayoutGuide.length;
    UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
    self.collectionView.contentInset = newInsets;

}

I found a better solution,use the undocumented method of UINavigationController.

#import <UIKit/UIKit.h>

@interface UINavigationController (ContentInset)


- (void) computeAndApplyScrollContentInsetDeltaForViewController:(UIViewController*) controller;

@end

#import "UINavigationController+ContentInset.h"

@interface UINavigationController()


- (void)_computeAndApplyScrollContentInsetDeltaForViewController:(id)arg1; 

@end


@implementation UINavigationController (ContentInset)

- (void) computeAndApplyScrollContentInsetDeltaForViewController:(UIViewController*) controller
{
    if ([UINavigationController instancesRespondToSelector:@selector(_computeAndApplyScrollContentInsetDeltaForViewController:)])
        [self _computeAndApplyScrollContentInsetDeltaForViewController:controller];
}

@end

then,do like this

- (void) cycleFromViewController: (UIViewController*) oldC
                toViewController: (UIViewController*) newC
{
    [oldC willMoveToParentViewController:nil];                        
    [self addChildViewController:newC];
  
    [self transitionFromViewController: oldC toViewController: newC   
                              duration: 0.25 options:0
                            animations:^{
                                newC.view.frame = oldC.view.frame;                                    
                                [self.navigationController computeAndApplyScrollContentInsetDeltaForViewController:newC];
                            }
                            completion:^(BOOL finished) {
                                [oldC removeFromParentViewController];                  
                                [newC didMoveToParentViewController:self];
                            }];
}