Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Easiest way to support multiple orientations? How do I load a custom NIB when the application is in Landscape?

I have an application in which I would like to support multiple orientations. I have two .xib files that I want to use, myViewController.xib and myViewControllerLandscape.xib. myViewController.xib exists in project/Resources and myViewControllerLandscape.xib exists in the root project directory.

What I want to do is use a separate NIB (myViewControllerLandscape.xib) for my rotations. I try detecting rotation in viewDidLoad like this:

if((self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft) || (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight))
 {
  NSLog(@"Landscape detected!");
  [self initWithNibName:@"myViewControllerLandscape" bundle:nil];

 }

But I can see in gdb that this isn't executed when the app is started with the device in landscape. The NSLog message doesn't fire. Why is this? What have I done wrong?

Also, if I explicitly put the initWithNibName function call in the viewDidLoad method, that nib is not loaded, and it continues with the myViewController.xib file. What's wrong with my call? Should I specify a bundle?

Thanks!

like image 433
Peter Hajas Avatar asked Mar 22 '10 23:03

Peter Hajas


4 Answers

I found a much better way that works independent of the nav controller. Right now, I have this working when embedded in a nav controller, and when NOT embedded (although I'm not using the nav controller right now, so there may be some bug I've not seen - e.g. the PUSH transition animation might go funny, or something)

Two NIBs, using Apple's naming convention. I suspect that in iOS 6 or 7, Apple might add this as a "feature". I'm using it in my apps and it works perfectly:

  1. triggers on WILL rotate, not SHOULD rotate (waits until the rotate anim is about to start)
  2. uses the Apple naming convention for landscape/portrait files (Default.png is Default-landscape.png if you want Apple to auto-load a landscape version)
  3. reloads the new NIB
  4. which resets the self.view - this will AUTOMATICALLY update the display
  5. and then it calls viewDidLoad (Apple will NOT call this for you, if you manually reload a NIB)
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    if( UIInterfaceOrientationIsLandscape(toInterfaceOrientation) )
    {
        [[NSBundle mainBundle] loadNibNamed: [NSString stringWithFormat:@"%@-landscape", NSStringFromClass([self class])]
                                      owner: self
                                    options: nil];
        [self viewDidLoad];
    }
    else
    {
        [[NSBundle mainBundle] loadNibNamed: [NSString stringWithFormat:@"%@", NSStringFromClass([self class])]
                                      owner: self
                                    options: nil];
        [self viewDidLoad];
    }
}
like image 82
Adam Avatar answered Nov 01 '22 15:11

Adam


You have to load one view, then check orientation and load another if needed. You check orientation in shouldAutorotateToInterfaceOrientation: returning yes if you want to rotate.

I use a navigation controller to manage the transition. If I have the portrait view up and the device rotates, I push the landscape view and then pop the landscape view when it return to portrait.

Edit:

I return YES for all orientations in shouldAutorotateToInterfaceOrientation: but will this be called when the app launches? Do you push your view inside of this function?

The orientation constants are not globals you query but rather part of the messages sent the controller by the system. As such, you cannot easily detect orientation before a view controller loads. Instead, you hardwire the app to start in a particular orientation (usually portrait) and then immediately rotate. (See mobile Safari. It always starts in portrait and then rotates to landscape.)

These are the two methods I used to swap out my portrait and landscape views.

All three view controllers have this method:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

The portrait has this:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {

    if (toInterfaceOrientation==UIInterfaceOrientationLandscapeRight) {
        [self.nav pushViewController:rightLVC animated:NO];
    }
    if (toInterfaceOrientation==UIInterfaceOrientationLandscapeLeft) {
        [self.nav pushViewController:leftLVC animated:NO];
    }
}

Each landscape controller has this:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {

    if (toInterfaceOrientation==UIInterfaceOrientationPortrait) {
        [self.nav popViewControllerAnimated:NO];
    }

The app starts in portrait. If the orientation of the device is landscape, it pushes the appropriate landscapes. When the device rotates back to portrait, it pops the landscape. To the user it looks like the same view reorganizing itself for a different orientation.

like image 33
TechZen Avatar answered Nov 01 '22 15:11

TechZen


Really like Adam's answer - I modified it slightly to allow for initial loading of nib in either portrait or landscape

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    if( !nibNameOrNil )     nibNameOrNil = [self nibNameRotated:[[UIApplication sharedApplication] statusBarOrientation]];
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    return self;
}

- (NSString*) nibNameRotated:(UIInterfaceOrientation)orientation
{
    if( UIInterfaceOrientationIsLandscape(orientation))     return [NSString stringWithFormat:@"%@-landscape", NSStringFromClass([self class])];
    return [NSString stringWithFormat:@"%@", NSStringFromClass([self class])];
}

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    NSString *nibname = [self nibNameRotated:toInterfaceOrientation];
    [[NSBundle mainBundle] loadNibNamed:nibname owner:self options:nil];
    [self viewDidLoad];
}
like image 5
amergin Avatar answered Nov 01 '22 16:11

amergin


I like TechZen's accepted answer but as pointed out in Adam's comment it leaves the portrait view controller in the navigation stack when you rotate to landscape. This means tapping the back button in the navigation bar while in landscape will take the user to the portrait view. A coworker and I are trying the approach of manually removing the extra item from the nav stack. It seems to work but I have very little experience with it yet, so I make no promises. Here is sample code:

To go from portrait to landscape:

[[self navigationController] pushViewController:landscapeViewController animated:NO];
[self removeFromNavigationStack:[MyPortraitViewController class]];

To go back to portrait:

[[self navigationController] pushViewController:portraitViewController animated:NO];
[self removeFromNavigationStack:[MyLandscapeViewController class]];

If you don't use animated:NO you may get warnings regarding the state of the navigation.

Helper:

- (void)removeFromNavigationStack:(Class)oldClass {
      UINavigationController *nav = [self navigationController];
      NSMutableArray *newStack = [NSMutableArray array];
      for (UIViewController *vc in nav.viewControllers) {
          if (![vc isMemberOfClass:[oldClass class]]) {
              [newStack addObject: vc];            
          }
      [nav setViewControllers:newStack animated:NO];
  }
like image 1
Deanna Gelbart Avatar answered Nov 01 '22 16:11

Deanna Gelbart