1

So, I am trying to achieve having a fixed top banner on Application UIWindow and a working navigation of multiple view controllers. I know there are other ways of performing this but for my project requirements I need to use xibs.

The code below allows to achieve this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    self.headerView = [[UIImageView alloc] init];
    self.headerView.frame = CGRectMake(0.0f, 20.0f, 320.0f, 44.0f);
    self.headerView.image = [UIImage imageNamed: @"header-banner-background.png"];
    [self.window addSubview:self.headerView];

    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];

    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:self.viewController];

    self.window.rootViewController = navigationController;
    [self.window makeKeyAndVisible];
    return YES;
}

Then in each ViewController viewWillAppear method I have resized both self.view and self.navigationController.navigationBar.frame in order to show themselves below the banner:

self.navigationController.navigationBar.frame = CGRectMake(0, 64, 320, 44);
self.view.frame = CGRectMake(0, 64, 320, 396); 

This is fine, works exactly the way I wanted.

But if the user presses the home button and the application resigns active, when it becomes active again the last viewController seen get's displayed but without the UIWindow banner, and the self.view and navigationBar are displayed on top.

Of course the current UIViewController viewWillAppear method is not called when the application returns active, but even if I put the resize code in another function and call it through a notification it does not work. But when the user presses a bar item and moves in the navigation stack, then everything works again and UIWindow displays the headerView.

Anyone understands why the UIWindow headerView disappears when application returns active?

EDIT 1: Possible solution?

@interface RootViewController ()
{
    ViewController *viewController;
    UINavigationController *navigationController;
}

@end

@implementation RootViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
        viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
        navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
    }
    return self;
}

- (void)loadView
{
    // Implement loadView to create a view hierarchy programmatically, without using a nib.
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 88)];

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
    [imageView setImage:[UIImage imageNamed:@"header"]];

    [view addSubview:imageView];

    navigationController.navigationBar.frame = CGRectMake(0, 44, 320, 44); 

    [view addSubview:navigationController.navigationBar];

    self.view = view;

}

EDIT 2: Second possible solution, still not working

- (void)loadView
{
    NSLog(@"loadView root");

    // Implement loadView to create a view hierarchy programmatically, without using a nib.
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 88)];

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
    [imageView setImage:[UIImage imageNamed:@"banner"]];
    [view addSubview:imageView];

    navigationController = [[UINavigationController alloc] init];
    navigationController.navigationBar.frame = CGRectMake(0, 43, 320, 44); 
    [view addSubview:navigationController.navigationBar];

    [self addChildViewController:navigationController];

    self.view = view;
    [self.view setBackgroundColor:[UIColor yellowColor]];
}

This is the result, which seems fine, without the push of the viewController1 (note the yellow background is from the rootViewController):

Without push

This instead is the result of adding:

- (void)viewDidLoad
{
    viewController1 = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil];
    [navigationController pushViewController:viewController1 animated:YES];    
}

With PUSH

The new view controller is not pushed (the background should be black) and the navigation bar has disappeared.

EDIT 3: Olloqui solution, still not working

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    [self.window setBackgroundColor:[UIColor redColor]];

  UIView *bannerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
    [self.window addSubview:bannerView];

    UIView *navigationView = [[UIView alloc] initWithFrame:CGRectMake(0, 44, 320, 416)];

    ViewController1 *viewController1 = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil];
    self.navigationController = [[UINavigationController alloc] initWithRootViewController:viewController1];
    self.navigationController.navigationBar.frame = CGRectMake(0, 0, 320, 44); 

    [navigationView addSubview:self.navigationController.navigationBar];

    [self.window addSubview:navigationView];

    self.window.rootViewController = self.navigationController;

    self.window.autoresizesSubviews = YES;

    [self.window makeKeyAndVisible];
    return YES;
}

Result is I have only the banner, viewController1 is pushed but no navigation bar is displayed. I really need to figure this out, anyone has a different approach?

Fabrizio Prosperi
  • 1,398
  • 4
  • 18
  • 32
  • Why not put the banner in your views in the xib itself? Then the uiviewcontroller will just manage it together with the rest of the view. – onnoweb May 03 '12 at 15:26
  • @onnoweb, that's why I specified "fixed" top banner. I don't want the navigation to affect the banner, the banner has to stay still while the user navigates multiple view controllers. – Fabrizio Prosperi May 03 '12 at 15:33
  • If this is iOS 5 only you should also call `[self addChildViewController:navigationController]` in `load view`. – Mr. Berna May 03 '12 at 19:29
  • @Mr.Berna actually should be iOS > 4.3, but anyway, for now let's say this is ok, anyway I must have misunderstood you because it is not working, I have edited the question again. Thanks for any additional hints. – Fabrizio Prosperi May 03 '12 at 23:42

4 Answers4

1

The window expects to have a root view controlled by its rootViewController property. When returning from the background the window only asks for the rootViewController to restore the appearance of that view controller's view. It doesn't ask all subviews to refresh.

To fix this I would create a new root view controller. This would have the banner and the navigation controller. That navigation controller would then hold your normal view controllers and manage the navigation. The banner will appear properly upon the app returning from the background. Your banner will be stationary while navigation occurs within the navigation controller.

Edit: I whipped up a little example program.

I started with a simple single view app template. I added two view controllers for the content. The just have labels for differentiation, and one has a button that loads the second view.

All the magic happens in the main view controller's viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
    OneViewController *oneVC = [[OneViewController alloc] initWithNibName:@"OneViewController" bundle:nil];
    self.navController = [[UINavigationController alloc]initWithRootViewController:oneVC];
    self.navController.view.frame = CGRectMake(0.0, 64.0, 320.0, 416.0);
    [self.view addSubview:self.navController.view];
    [self addChildViewController:self.navController];   
}

The nib for this view loads the image view for the banner.

The example project in on Bitbucket.

Mr. Berna
  • 10,525
  • 1
  • 39
  • 42
  • In fact, this is very similar to what I propose in my answer, but using a viewcontroller that will make "the proxy" for the rootController. What I dont like from this proxy is that it has to propagate all the events such as roations, appearances,... unless you can use the containerController in iOS5. – Angel G. Olloqui May 03 '12 at 15:54
  • @MrBerna I will try to follow your suggestion and let you know if it works, thank you. – Fabrizio Prosperi May 03 '12 at 18:44
  • I am having some problems with Xcode right now but in the meantime I post the rootViewController code, was this your idea? – Fabrizio Prosperi May 03 '12 at 19:20
  • That's pretty neat and working perfectly MrBerna thanks, it is basically the same idea of my solution, which instead of extending the view of the navigation controller, extends the view of UITabBarController, but definitely better yours. Thank you very much! – Fabrizio Prosperi May 06 '12 at 02:39
  • Just a quick note @MrBerna (thanks for naming the project after my name). As stated [here](http://stackoverflow.com/questions/9261171/addchildviewcontroller-alternative-for-ios-4-3) I tried commenting out the addChildViewController method, and everything still works, so I think this can be used also for any iOS > 4.3 device. Do you agree? – Fabrizio Prosperi May 06 '12 at 08:55
1

I have never tried it before but I would take a shot to create 2 views in the UIWindow, one for the banner and the other one for the navigation + content. Then, insert the navigationBar view in this second view, that will be sized properly. I am not sure if the navigation is going to try to take the full window are, but resizing the navigation controller on each viewWillAppear doesnt look a good solution to me. Try it and comment the result!

Angel G. Olloqui
  • 8,045
  • 3
  • 33
  • 31
  • thank you but I am not sure what you mean with "Then, insert the navigationBar view in this second view, that will be sized properly", right now I am just using the UINavigationBar provided by the self.window.rootViewController and then the self.navigationController.pushViewController:animated method. Could you please clarify? – Fabrizio Prosperi May 03 '12 at 18:42
  • I just wanted to say that you should add de view of the navigation controller to this subview. Example: [subview2 addSubview:navBar.view] – Angel G. Olloqui May 03 '12 at 20:30
  • @Olloqui, now it's clearer, anyway I agree is almost what I am trying to achieve with Mr.Berna suggestion, even if not successfully so far. Thank you. – Fabrizio Prosperi May 03 '12 at 23:43
  • @Olloqui, I have tried also your suggestion, EDIT3 above, but no luck, maybe I didn't get your point? – Fabrizio Prosperi May 04 '12 at 11:11
1

I think you can try to workaround this problem by adding the banner view to window.rootViewController.view like this:

[window.rootViewController.view addSubview:bannerView]
jianpx
  • 3,190
  • 1
  • 30
  • 26
0

Ok, I really needed to solve this ASAP, therefore I opted for a solution I was lucky enough to find here. I still have to solve the management of the UITabBarController added on bottom but the rest seems to be working fine. Thanks everyone for help and additional hints.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    [self.window setBackgroundColor:[UIColor redColor]];

    // Create a view  for the banner add the banner to this view
    UIImageView *bannerImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 45)];
    bannerImageView.image = [UIImage imageNamed:@"banner"];

    // put this banner above the nav bar 
    UIView *bannerView = [[UIView alloc] initWithFrame:CGRectMake(0, -44, 320, 44)];
    [bannerView addSubview:bannerImageView];

    //add the tabBarController as a subview of the view
    UITabBarController *tabBarController = [[UITabBarController alloc] init];
    [tabBarController.view addSubview:bannerView];

    // Move the root view to show status bar & banner
    tabBarController.view.frame = CGRectMake(0, 20+44, 320, 480-20-44); 

    //add the modified logo view to window
    [self.window addSubview:tabBarController.view];

    ViewController1 *viewController1 = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil];
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController1];

    tabBarController.viewControllers = [NSArray arrayWithObjects:navigationController, nil];
    //tabBarController.tabBar.hidden = YES;

    [self.window setRootViewController:tabBarController];

    [self.window makeKeyAndVisible];
    return YES;
}
Community
  • 1
  • 1
Fabrizio Prosperi
  • 1,398
  • 4
  • 18
  • 32