In my application I use an Alternate Landscape Interface strategy (present your landscape view as a modal). I also use a navigation controller for transitioning and this causes the following problem: I dunno how to push/pop correctly from landscape orientation.
I came up with the following solution, but someone may know a better one. Suppose one has to deal with only two views. Let's call them AP, AL, BP, BL, where the second letter stands for orientation. We start with a navigation controller with AP inside. To go between AP and BP we just push/pop. To go from AP to AL we present a modal navigation controller with AL inside. To go between AL and BL we push/pop inside the second navigation controller. Now to go from BP to BL we pop w/o animation and present a modal navigation controller with BL sitting on top of AL. To go from BL to BP we dismiss the modal navigation controller and push BP w/o animation.
Seems to be a bit ugly, but not so bad. Can anyone think of something better?
Thanks in advance!
Is there some reason you need to present your landscape orientation as modal in a separate controller? When I have two entirely different views for my portrait and landscape orientations I fade between them as they stretch during the rotation.
This allows for vastly different content in both orientations, a nice transition between them, and shared code under one controller.
Here is some code. Our UIViewController will switch between portraitView and landscapeView when we change orientation.
portraitView and landscapeView are both children of the UIViewController's view. The hierarchy looks as follows:
UIViewController
|
- view
|
|- portraitView
|
|- landscapeView
Both have their autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight to ensure that they stretch as the view controller rotates.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if( orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight )
{
[UIView animateWithDuration:duration
animations:^
{
//Fade the landscape view over the top of the
//portrait view as during rotation
landscapeView.alpha = 1.0f;
}
completion:^(BOOL finished)
{
//Hide the portrait view when landscape is fully
//visible
portraitView.alpha = 0.0f
}];
}
else
{
//Show the portrait view (underneath the landscape view)
portraitView.alpha = 1.0f;
[UIView animateWithDuration:duration
animations:^
{
//Fade out the landscape view to reveal the portrait view
landscapeView.alpha = 0.0f;
}];
}
}
Your controls and subviews will fade and deactivate along with the appropriate views, allowing you to have completely different content. I used this recently to fade between two different background images when changing orientation. The effect is very smooth.
You can now create your two view controllers, A and B which each manage two views as described above. You can then simply push the view controllers as normal and not have to worry about managing the UINavigationController's view controller stack during rotation.
Related
I have a multiview app with a Toolbar. The Root view controller controls the toolbar, but the other views have their own view controller class. I use code like this to load the views:
-(IBAction)loadBand1Start:(id)sender{
[self clearView];
Band1Start *band1Controller = [[Band1Start alloc] initWithNibName:#"Band1Start" bundle:nil];
self.band1Start = band1Controller;
[band1Controller release];
[self.view insertSubview:band1Start.view atIndex:0];
if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
band1Start.view.frame=CGRectMake(0, 0, 480.0, 260.0);
} else {
band1Start.view.frame=CGRectMake(0, 0, 320.0, 400.0);
}
}
The IF statement is to make it load in the correct orientation. Without this, although it rotates ok, it always loads in Portrait, even if the phone is in landscape.
Autosize and Auto Rotate works. I used IB to build my nibs in portrait. When the phone is rotated to landscape, they do rotate and resize, however with some nibs, the rotated layout does not look right. There is overlaps etc, which is a common problem, and I know can be fixed using CGRectMake.
However, it does not matter where I put the CGRectCode, it does not execute. I have tried putting it in willRotateToInterfaceOrientation, willAnimateRotationToInterfaceOrientation, and viewWillAppear, but the rotated layout is not changed.
If I use the code below to switch views, the rotated layout is how I want it, but the toolbar does not appear:
-(IBAction)loadBand1Start:(id)sender{
[self clearView];
Band1Start *band1Controller = [[Band1Start alloc] initWithNibName:#"Band1Start" bundle:nil];
[self presentModalViewController:band1Controller animated:NO];
[band1Controller release];
}
I think this is because the view controller for the toolbar needs to be the root view controller for the toolbar to appear, but only the root view controller receives the orientation change notifications.
So if I keep my toolbar controller as the root view controller, the subview's controller does not receive the orientation notifications, so does not execute my CGRectMake code, but if I make the subview's controller the root view controller, it receives the notifications ok, but the toolbar does not appear because it's controller is not the root controller.
Am I correct about the problem here? Is there a way around it?
I have a child view controller that needs to be always shown in landscape mode regardless of which mode the parent view is in. I'm adding it onto the parent's view stack with
[[self navigationController] pushViewController:controller animated:NO];
I am trying to force the view to rotate and be displayed as if the device is held in Landscape orientation even though it's still held in Portrait orientation.
The only problem is that no matter what size and coordinates I set the view frame to, I see a 20 pixel gap on the right side of the screen where the StatusBar used to be during Portrait Mode.
What can I adjust to ensure that this gap is gone?
Here's how I'm doing the transformation (as recommended by this SO article)
- (void)changeOrientationToLandscape
{
UIApplication *myApp = [UIApplication sharedApplication];
CGAffineTransform landscapeTransform = CGAffineTransformMakeRotation( degreesToRadian(90) );
landscapeTransform = CGAffineTransformTranslate(landscapeTransform, +90.0, +90.0 );
[self.view setTransform:landscapeTransform];
// Change the status bar orientation so it's shown as if we're in landscape
myApp.statusBarOrientation = UIInterfaceOrientationLandscapeLeft;
// Manually force view frame - this doesn't seem to fix the 20 pixel gap
self.view.frame = CGRectMake(0, 0, 480, 320);
}
The only time I see the view taking up the entire screen and without the 20 pixel gap is if I hide the status bar all together, something I cannot do for this app.
Here's how the screen looks (it's held in Portrait orientation with the home button on the bottom). Notice how the status bar ends doesn't have the same purple background - I was hoping I could shift the view over so the white gap is no longer present.
I also printed out the view and navigationController's view frames and they both report
x and y location at 0,0. The navigation view frame's reported dimension is 320x480 while view's frame is 480x320.
What about disabling the status bar when child is pushed and enabling when it's "popped"?
You can hide status bar for your child view controller only.
If you're pushing your child view controller via self.navigationController, simply override shouldAutorotateToInterfaceOrientation in child view controller and put this ...
return UIInterfaceOrientationIsLandscape( interfaceOrientation );
... to body of this method to make it landscape only.
I'm having the landscape mode issue and I can not find the way out. Basically, I'm having a tab bar application and in the first tab i have navigation controller. In this navigation controller, first view contains table with items and after clicking the item, detail view describing the item is pushed.
I need to implement landscape mode for both list and detail view, but for list view, i need to use different view controller for landscape mode (generally, something like cover flow). Detail view is just changing orientation and no need to use alternate view controller in this case.
I tried to achieve this behaviour by implementing modal view controller for list view controller, according to Alternate Views example by Apple. This works fine when I'm in list view (when I turn device into landscape mode, cover flow view controller is correctly presented). Problem comes when I'm showing detail view. When I change the device orientation, cover flow shows up again. What I expected is that cover flow will be presented only in case that list view is on the screen. It seems like modal view controller is always visible no matter what VC is currently on the stack of NC.
It seems to me that presenting modal VC as landscape view for particular VC is not working for multiple navigation levels.
I also tried to add landscape view as a subview into view controllers view. When using this solution, i have no problem with navigation levels, but issue here is that tab bar is not hidden in landscape mode. I need to hide tab bar for cover flow, which is achieved by presenting modal VC.
I will appreciate any help with this issue.
Great thanks!
In the detail view controller, you could set up a different view entirely using something like this (code from a recent project of mine):
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toOrientation
duration:(NSTimeInterval)duration
{
if ([graphView superview]) {
if (toOrientation == UIInterfaceOrientationPortrait ||
toOrientation == UIInterfaceOrientationPortraitUpsideDown) {
[graphView removeFromSuperview];
}
} else {
if (toOrientation == UIInterfaceOrientationLandscapeLeft ||
toOrientation == UIInterfaceOrientationLandscapeRight) {
[[self view] endEditing:YES];
[[self view] addSubview:graphView];
}
}
}
And now to hide the tabbar when you are in landscape (bit of a hack, but works):
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
UIInterfaceOrientation toOrientation = self.interfaceOrientation;
if ( self.tabBarController.view.subviews.count >= 2 )
{
UIView *transView = [self.tabBarController.view.subviews objectAtIndex:0];
UIView *tabBar = [self.tabBarController.view.subviews objectAtIndex:1];
if(toOrientation == UIInterfaceOrientationLandscapeLeft ||
toOrientation == UIInterfaceOrientationLandscapeRight) {
transView.frame = CGRectMake(0, 0, 480, 320 );
tabBar.hidden = TRUE;
}
else
{
transView.frame = CGRectMake(0, 0, 320, 480);
tabBar.hidden = FALSE;
}
}
}
For this project, I added a view called "graphView" that I wanted to appear if and only if in landscape mode, and then I wanted to tabbar to be hidden. This sounds similar to what you're after, I think.
The only potential problem I foresee is that if you enter landscape mode before the detail view is pushed, things could get wonky. Therefore you may want to use these methods in the list view controller instead. This particular problem never arose for me, but it's something I thought about before I realized it was moot.
I understand that I can change what interface orientations are supported, but if I want the landscape view to be entirely different, or somewhat different than the portrait view, how do I code for it?
Thank you.
Just load another view controller when the orientation changes. To make things simple, I often use a hidden navigation controller and push and pop the views I want for any particular orientation.
Using a dedicated view controller for each orientation is the easiest approach.
If the only difference is presentation, not controller logic, then you could also try coding a single view controller to swap between two views depending on orientation.
E.g. pseudocode
UIView *landscapeView = ...;
UIView *portraitView = ...;
when orientationChanged
{
if landscape then
[portraitView setHidden:YES];
[landscapeView setHidden:NO];
self.view = landscapeView;
else if portrait then
[landscapeView setHidden:NO];
[portraitView setHidden:YES];
self.view = portraitView;
[self.view setNeedsDisplay];
}
Here is my setup: one navigation controller, two views
The first view, the rootview, displays the statusbar and navigation bar (portrait view).
When the rootviewController senses rotation, it hides the nav bar and status bar and then it pushes view 2 onto the navigation controller (in landscape now).
This part works as expected.
When View2 viewcontroller senses rotation (back to portrait), it pops itself of the navigation controller, revealing view 1. View 1 then unhides the status bar and nav bar.
From here, it gets weird. Depending on when I unhide the navigation bar, I get strange results. As you can see below (The beaker photo should be just below the navbar at the top of the screen).
I have tried unhiding the navigation bar in:
viewWillRotate/viewDidRotate of view 2
viewWillAppear/viewDidAppear of view 1
poptoRootView in the navigation controller (I subclassed just to try)
Nothing works. Any Idea what is going on? This should be simple, but maybe I am doing things in the wrong places.
Check your autoresizing mask on all views in your nib and make sure its all set properly. If you have anything the way its not supposed to be it will freak out on rotation.
Not sure why it's necessary, but when you're swaping views, you must apply a transformation to your view (with only 1 view the iPhone does this for you), and you must set the bounds of it.
Here is the code that should work for you (on the willAnimateFirstHalfOfRotationToInterfaceOrientation):
#define degreesToRadians(x) (M_PI * (x) / 180.0)
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
self.view = landscapeView;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadians(90));
self.view.bounds = CGRectMake(0.0, 0.0, 480.0, 320);
} else {
self.view = portraitView;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadians(0));
self.view.bounds = CGRectMake(0.0, 0.0, 300, 480);
}
In the end, I reworked my app. (to use a modal view controller)
But I came into similar issues, instead white space where the status bar was located.
I think both problems can be attributed to not talking to the Navigation Controller when rotating/resizing views (instead I was talking to the ViewController.
For details of how I solved the problem:
iPhone + CGAffineTransFormRotate(pi/2) + statusBarHidden:YES + presentModalViewController = 20 pixels of white space