Rotating view leaves 20 pixel gap on one side - iphone

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.

Related

How to programmatically create UIView with landscape orientation?

I have an iPad application with fixed landscape orientation. Everything works OK until the moment when I programmatically create UIView and add it to my root UIViewController's view - it's orientation is always set to portrait while all other content is displayed in landscape mode. This UIView is a modal view showing some custom form for user.
AlertView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height)];
AlertView.opaque = NO;
AlertView.backgroundColor = [UIColor blackColor];
AlertView.alpha = 0.7;
<...> // some other configuration code
[window addSubview:AlertView];
So the question is: how can I initiate UIView with landscape orientation? All I can think of is using transform like:
modalView.transform = CGAffineTransformMakeRotation( ( 180 * M_PI ) / 360 );
Though it's not too elegant way...
The reason your AlertView gets displayed in portrait orientation while the rest of your app gets displayed in landscape is because you are adding the AlertView as a subview of your window rather than adding it as a subview of a view controller's view. This places it outside of the view hierarchy that gets transformed automatically through autorotation.
The app's window object coordinates autorotation by finding its topmost subview that has a corresponding view controller. Upon device rotation, the window calls shouldAutorotateToInterfaceOrientation: on this view controller and transforms its view as appropriate. If you want your view to autorotate, it must be a part of this view's hierarchy.
So instead of [window addSubview:AlertView];, do something like [self.view addSubview:AlertView];.
You should try setting an autoresizing mask.
When the ViewController rotates, your AlertView's bounds would change, too.
yourView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
To make UIViews in landscape orientation, and provided you want your root view controller to always be in portrait mode, you have 2 options:
Do it the way you are doing it by rotating the UIView using a transform
Change the way you are doing it to allow
your root view controller to rotate (easiest). The view controller will then handle the rotations of your UIViews for you

UINavigationController and Alternate Landscape

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.

iPhone's UITabBar as part of application window; problems when rotating device

I have an iPhone application that's using Navigation Controller to display the top bar (with title and back button, and such...).
I added a UITabBar to the application window, that enables to switch between the parts of it. Instead of adding the tab bar to each of ViewController's view I added the bar to app window.
(When I had it in the ViewController, switching between controllers made the tab bar to swipe left/right, when animated pop/push occured, together with whole view).
So, I added the UITabBar to the MainWindow.xib, and tied it to the app delegate's variable. In my didFinishLaunchingWithOptions method, I added the following code:
[self.window addSubview:navigationController.view];
CGRect frame = navigationController.view.frame;
frame.size.height -= tabbar.frame.size.height;
navigationController.view.frame = frame;
tabbar.selectedItem = [tabbar.items objectAtIndex:0];
to resize the main (navigationController's) view, in order to make the TabBar visible.
The problem shows up when I rotate the device -- my view gets stretched to full window and I loose the ability to show the TabBar.
I added a - (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation) fromInterfaceOrientation method to my ViewController, with the following code:
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
CGRect frame = self.view.frame;
frame.size.height -= [AppState shared].tabBar.frame.size.height;
//frame.origin.y = [AppState shared].tabBar.frame.size.height;
//frame.origin.x = 100;
self.view.frame = frame;
frame = [AppState shared].tabBar.frame;
frame.origin.y = [UIScreen mainScreen].bounds.size.height - frame.origin.y - frame.size.height;
[AppState shared].tabBar.frame = frame;
}
It resizes the view, and moves the tab bar to up/down part of the view (I allow only Portrait/Portrait upside down orientations here). The problem is, my TabBar is turned upside down as well, and also, it's no longer clickable.
It looks like the image below:
Anyone knows how to handle this kind of situation? Or, how to make the tab bar not tied to view controller, but also able to handle interface's rotation smoothly?
You are using the tabbar in an unintended way. You seem to be using the UITabBarView as an uncontrolled element of other views. That is not it's function.
The UITabBarView should be controlled directly by a UITabBarController which in turn should be controlling all the view controllers for the views displayed in the tabbar i.e. the tabbar controller is a type of navigation controller that controls subcontrollers.
Suppose you have three tabs and the third one is a navigation controller. Your controller hierarchy would look like this:
TabbarController:
-->tab1ViewController
-->tab2ViewController
-->tab3ViewController(UINavigationController):
-->rootViewController-->secondViewController
You are trying to move and manage the tabbar view without its controller and the proper controller hierarchy. That isn't going to work.

What is the best way to move a UIToolbar?

Here is an interesting problem. On the iPhone, I have a view set up that has a toolbar on the bottom of the screen. I am currently trying to make this a universal app so that it runs on iPad as well. I would like the toolbar to be at the top of the iPad screen, so in the viewDidLoad method of the specific viewController I have the following code.
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
//move the toolbar to the top of the page and move the webview down by the height of the toolbar
CGRect toolBarFrame = self.aToolBar.frame;
CGRect webFrame = self.aWebView.frame;
webFrame.origin.y = toolBarFrame.size.height;
[self.aWebView setFrame:webFrame];
toolBarFrame.origin.y = 0;
[self.aToolBar setFrame:toolBarFrame];
[Utils errorString:[NSString stringWithFormat:#"origen: x=%f y=%f", self.aToolBar.frame.origin.x, self.aToolBar.frame.origin.y]];
[Utils errorString:[NSString stringWithFormat:#"origen: x=%f y=%f", self.aWebView.frame.origin.x, self.aWebView.frame.origin.y]];
}
The problem I am having is that the webView moves down fine, but the toolbar only moves up to about what seems to be the height of a iPhone screen. The call to errorString tells me that the webView's origin is at 0,44 (where it should be) and that the toolbar's origin is at 0,0, but it is actually somewhere in the middle of the screen!
Anybody have a clue what is going on here?
I'd say this is because the frame is being set to the top of an iPhone frame (so 320px from the bottom), and then afterwards the view is being resized to fit the iPad screen. However UIToolbar by default is set to stick to the bottom of the window (fixed bottom margin, and flexible top margin) so it's staying 320px from the bottom.
To fix this try:
[self.aToolbar setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin];
before setting the toolbar's frame (if it doesn't work before setting the frame, try putting it after)

Rotation affecting Navigation Bar unpredictably

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