Home

iPhone programming: how to switch to a landscape view at the moment of your choosing

Posted by Simon on February 27, 2009 at 05:12 PM

Categories: code, mobile, iphone

Maybe someday, Apple will make it easy to rotate manually into a landscape view. But right now it's been causing me enormous headache with hideous frames issues. Running an app in landscape the whole time is easy, but doing just some views in landscape is insane, especially if you're trying to switch while in the middle of a navigation controller.

However I've found an easy solution which is to use a new window. Just reset the whole view problem. You can then fake out the navigation bar using the method of your preference. Here's the bare bones. For me, I'm going from a tableView, click on an item and get a "results view".

// In ResultsListController, a UITableView delegate:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  ResultsViewController * resultsViewController = [[[ResultsViewController alloc] initWithNibName:@"ResultsViewController" bundle:nil] autorelease];
  resultsViewController._recordIndex = indexPath.row;
  UIWindow * window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // this is a leak!
  window.backgroundColor = [UIColor redColor]; // just for debugging
  [window addSubview:resultsViewController.view];
  [window makeKeyAndVisible];
}

Now ResultsViewController has its own NIB, and the view is set to be sideways in IB.

// In ResultsViewController
- (void)viewWillAppear:(BOOL)animated; {
// First rotate the screen:
  [UIApplication sharedApplication].statusBarOrientation = UIInterfaceOrientationLandscapeRight;
// Then rotate the view and re-align it:
  CGAffineTransform landscapeTransform = CGAffineTransformMakeRotation( degreesToRadian(90) );
  landscapeTransform = CGAffineTransformTranslate( landscapeTransform, +90.0, +90.0 );
  [self.view setTransform:landscapeTransform];
}

// Connect your "back" button in the results view to this:
- (IBAction)back:sender; {
// return screen rotation to normal:
[UIApplication sharedApplication].statusBarOrientation = UIInterfaceOrientationPortrait;
// Get rid of the window, and the "normal" window will re-appear from underneath
self.view.window.hidden = YES;
[self.view.window resignKeyWindow];
}

Easy as cake!

UPDATE: There's follow-up Q&A on the iphonedevSDK forums.

Comments

There are 14 comments on this post. Post yours →

Marcio

What if you need to update the first window when the second one is released. I've noticed that the viewWillAppear method is not called for the first window when the second one is dismissed.

Toby

I've found this window disappears when the iphone awakes from sleep.

Can you confirm if that's the case?

simon

For both of you, can you hold onto a reference to your first window as a member variable in your controller. I'm using the same view controller for both windows. Then you can do whatever you want to the first window at any time.

Toby

Not sure if that's my problem.

Basically what happens is that if the iphone (not the simulator) is locked when this new window is open on the top, when it's awoken the top window is now invisible showing the old window underneath. It is still active however. I've found that if I have a button that resigns the second window I can still pres it (even if I can't see it) and then I can reload the new window on the top again. It seems the second window looses it's visible status on lock. I suspect it's a bug in that the iphone not really expecting 2 windows.

Can you test it on the version you have an see if it's the same (I have a nested tab bar controller above all of this so that might be connected.)

I might have to try and see if I can do a a notification on unlock and try and force the top window visible again. Not sure if that's possible but it's worth a try.

simon

I just tried it with my app and there was no problem. I turned off the phone using the top button. When I turned it back on, everything was fine, exactly how I left it.

I think that Apple probably uses this technique in their own apps (like iPod app).

Get rid of that nav controller maybe...

Toby

ok, that's encouraging. I'll have a play. Possibly the tab bar controller.

I may be able to force it.

Jimmy Hay

Could someone post their example code to get this to work cause I've been trying to force switches between landscape and portrait with a Nav Controller for a couple of days now and can't seem to get it working for love nor money. Cheers, Jimmy.

Shawn Stanley

Ugh. This seems very kludgy to me. Plus, Apple says not to create more than one window for your app.

My solution was to remove the navigation controller's view and add the landscape subview. When switching back, just remove the landscape view and re-add the nav controller's view. You can even animate it with a flip or a transition to make it look purdy.

Why is CGAffineTransformTranslate(,+90,+90)? How do you get 90?

simon

CGAffineTransformTranslate( landscapeTransform, +90.0, +90.0 );

Well, this does the landscape transform and then offsets by x=90 and y=90. Why those numbers? I don't remember exactly how I came to them... possibly by experimentation.

I was very pleased to find this site.I wanted to thank you for this great read!! I definitely enjoying every little bit of it and I have you bookmarked to check out new stuff you post.

I completely agree with the above comment, the internet is with a doubt growing into the most important medium of communication across the globe and its due to sites like this that ideas are spreading so quickly.

Terrence

This is the code I used, which is slightly different.

I'm worried about the memory leak and whether Apple would reject the app because of the UIWindow thing. Has anyone had an app accepted using this method?

// call this from button or didselectrow

  • (void) launchLandscape:(id)sender { LandscapeViewController *controller = [[LandscapeViewController alloc] initWithNibName:@"LandscapeView" bundle:nil];

    UIWindow * window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // LEAK! window.backgroundColor = [UIColor redColor]; // just for debugging [window addSubview:controller.view]; [window makeKeyAndVisible];

}

// inside the landscape view controller, whose view is setup in IB in landscape

  • (void)viewWillAppear:(BOOL)animated { [UIApplication sharedApplication].statusBarOrientation = UIInterfaceOrientationLandscapeRight; CGAffineTransform landscapeTransform = CGAffineTransformMakeRotation( degreesToRadian(90) ); landscapeTransform = CGAffineTransformTranslate( landscapeTransform, +90.0, +90.0 ); [self.view setTransform:landscapeTransform]; }

  • (void)viewDidLoad { [[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO]; [super viewDidLoad]; }

  • (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return (interfaceOrientation == UIInterfaceOrientationLandscapeRight); }

-(IBAction)backButton:(id)sender { [UIApplication sharedApplication].statusBarOrientation = UIInterfaceOrientationPortrait; [[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO]; self.view.window.hidden = YES; [self.view.window resignKeyWindow]; }

Terrence

I put my solution that doesn't need UIWindow shenanigans here: http://stackoverflow.com/questions/2922919/transitioning-to-landscape-rotation-within-a-uinavigationcontroller

Post a comment

Required fields in bold.

 

Browse Old Articles

Categories:

Popular posts:

Subscribe to:

Blogroll: