I would like to write a custom full-screen container view controller with the intention of putting a UINavigationController in it as a child view controller. The view of the UINavigationController will fill up the view of the container view controller so that it looks like the UINavigationController is the root view controller. (One would want to do something like this to, say, create the sliding sidebar menu UI popularized by Facebook.)
What I've done works EXCEPT there is a glitch when presenting another view controller that hides the status bar when the iPhone is in landscape orientation. Typically, the navigation bar slides up when the status bar disappears and slides down when it reappears. Instead, the navigation bar stays where it is when it is suppose to slide up, and when it is suppose to slide down, it is first positioned with the status bar overlapping it and then jumps to its correct position below the status bar. Basically, I'm trying to have the UINavigationController behave as it would if it were not inside a custom container view controller.
Below is some code that you can run to see the problem, but if you don't want to do that, just take a look at the ContainerViewController class, which implements a minimal custom container view controller. What is it that I'm missing in my custom container view controller that is causing this problem? It works when I use a UITabBarController as the container view controller, so it seems like I'm just missing something in my implementation.
MORE STUFF BELOW TO READ IF YOU WANT
If you would like to run the sample code to see the problem, here is an overview. There is a preprocessor definition called MODE defined in AppDelegate to conditionally compile the app in three ways.
When MODE == 1, ViewController is inside a UINavigationController. You can then press a "Present" button to present ViewControllerWithStatusBarHidden, and then you can press a "Dismiss" button to dismiss this view controller. This mode of the app shows the behavior that I am seeking.
When MODE == 2, we have the same thing as in MODE == 1 except that the UINavigationController is inside of a ContainerViewController. This mode of the app shows the undesirable behavior I have currently.
When MODE == 3, we have the same thing as in MODE == 1 except that the UINavigationController is inside of a UITabBarController. This mode of the app shows that it is possible to get the behavior I am seeking.
Again, to see the problem, just press the "Present" button and then the "Dismiss" button when the iPhone is in landscape orientation.
CODE
Four classes:
- ContainerViewController
- AppDelegate
- ViewController
- ViewControllerWithStatusBarHidden
ContainerViewController.h
#import <UIKit/UIKit.h>
@interface ContainerViewController : UIViewController
@property (nonatomic) UIViewController * viewController;
@end
ContainerViewController.m
#import "ContainerViewController.h"
// This custom container view controller only has one child view controller,
// whose view fills up the view of the container view controller.
@implementation ContainerViewController
- (UIViewController *)viewController {
if (self.childViewControllers.count > 0) {
return [self.childViewControllers firstObject];
}
else {
return nil;
}
}
- (void)setViewController:(UIViewController *)viewController {
UIViewController *previousViewController = [self.childViewControllers firstObject];
if ((previousViewController == nil && viewController != nil)
|| (previousViewController != nil && viewController == nil)
|| (previousViewController != nil && viewController != nil
&& previousViewController != viewController))
{
if (previousViewController != nil) {
// Remove the old child view controller.
[previousViewController willMoveToParentViewController:nil];
[previousViewController.view removeFromSuperview];
[previousViewController removeFromParentViewController];
}
if (viewController != nil) {
// Add the new child view controller.
[self addChildViewController:viewController];
self.viewController.view.frame = self.view.bounds;
self.viewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.viewController.view];
[self.viewController didMoveToParentViewController:self];
}
}
}
- (UIViewController *)childViewControllerForStatusBarHidden {
return self.viewController;
}
- (UIViewController *)childViewControllerForStatusBarStyle {
return self.viewController;
}
@end
AppDelegate.h
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
AppDelegate.m
#import "AppDelegate.h"
#import "ViewController.h"
#import "ContainerViewController.h"
#define MODE 2 // Mode can be 1, 2, or 3.
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
ViewController *vc = [[ViewController alloc] initWithNibName:nil bundle:nil];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
#if MODE == 1 // No container view controller.
self.window.rootViewController = nav;
#elif MODE == 2 // Use custom container view controller.
ContainerViewController *container = [[ContainerViewController alloc] initWithNibName:nil bundle:nil];
container.viewController = nav;
self.window.rootViewController = container;
#elif MODE == 3 // Use tab bar controller as container view controller.
UITabBarController *tab = [[UITabBarController alloc] initWithNibName:nil bundle:nil];
tab.viewControllers = @[nav];
self.window.rootViewController = tab;
#endif
[self.window makeKeyAndVisible];
return YES;
}
@end
ViewController.h
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
ViewController.m
#import "ViewController.h"
#import "ViewControllerWithStatusBarHidden.h"
// This view controller will serve as the content of a navigation controller.
// It also provides a button in the navigation bar to present another view controller.
@implementation ViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = @"Title";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Present"
style:UIBarButtonItemStylePlain
target:self
action:@selector(pressedPresentButton:)];
}
return self;
}
- (void)loadView {
self.view = [[UIView alloc] init];
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)pressedPresentButton:(id)sender {
ViewControllerWithStatusBarHidden *vc = [[ViewControllerWithStatusBarHidden alloc] initWithNibName:nil bundle:nil];
[self presentViewController:vc animated:YES completion:nil];
}
@end
ViewControllerWithStatusBarHidden.h
#import <UIKit/UIKit.h>
@interface ViewControllerWithStatusBarHidden : UIViewController
@end
ViewControllerWithStatusBarHidden.m
#import "ViewControllerWithStatusBarHidden.h"
// This view controller is meant to be presented and does two things:
// (1) shows a button to dismiss itself and (2) hides the status bar.
@implementation ViewControllerWithStatusBarHidden
- (void)loadView {
self.view = [[UIView alloc] init];
self.view.backgroundColor = [UIColor yellowColor];
}
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeSystem];
[dismissButton setTitle:@"Dismiss" forState:UIControlStateNormal];
[dismissButton addTarget:self
action:@selector(pressedDismissButton:)
forControlEvents:UIControlEventTouchUpInside];
dismissButton.frame = CGRectMake(100, 100, 100, 100);
[self.view addSubview:dismissButton];
}
- (void)pressedDismissButton:(id)sender {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
- (BOOL)prefersStatusBarHidden {
return YES;
}
//- (NSUInteger)supportedInterfaceOrientations {
// return UIInterfaceOrientationMaskPortrait;
//}
@end