I created a subclass of UITabBarController, in order to hide tabBar and statusBar once in landscape mode. I successfully implemented the code to hide/show the tabBar, but the stausBar is driving me crazy. My current implementation works 100% but not for the first rotation, and I'm unable to figure out why.
The code is the following:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
BOOL hide = (fromInterfaceOrientation == UIInterfaceOrientationPortrait ||
fromInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown);
[[UIApplication sharedApplication] setStatusBarHidden:hide withAnimation:UIStatusBarAnimationNone];
CGRect mainFrame = [[UIScreen mainScreen] applicationFrame];
[self.view setFrame:mainFrame];
}
In practice the first time I rotate my iPhone, the statusBar is correctly hide, but the frame is not correct (it has the 20px gap on the top). If I return to the portrait view from here, the layout will restore as expected, if then I rotate in landscape for the second time it will finally works as desired (no bars, pixel perfect layout!)... and from this point I can rotate my device N times and the views will always be presented in the right way...
so, why the first time my code fails?!
Extra info you may need:
root tab controllers are UINavigationControllers
all my nested view controllers are properly configured to support orientation changes
I'm testing using iOS 5
I can't believe it, but the solution is really simple! I solved by moving setStatusBarHidden:withAnimation: from didRotate... to willRotate..., the implementation is the following:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
BOOL show = (toInterfaceOrientation == UIInterfaceOrientationPortrait ||
toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown);
[[UIApplication sharedApplication] setStatusBarHidden:!show withAnimation:UIStatusBarAnimationNone];
}
In my case there is no need to hardcoding the new frame, since my views make use of autoresize masks... the view will be rendered properly automagically by UIKit... AWESOME :)
... +1 to virushuo for citing willRotateToInterfaceOrientation (which I was not taking into account)
Try UINavigationController class method setNavigationBarHidden:animated: in willRotateToInterfaceOrientation.
setNavigationBarHidden:animated:
Sets whether the navigation bar is hidden.
(void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated
Parameters
hidden
Specify YES to hide the navigation bar or NO to show it.
animated
Specify YES if you want to animate the change in visibility or NO if you want the navigation bar to appear immediately.
Discussion
For animated transitions, the duration of the animation is specified by the value in the UINavigationControllerHideShowBarDuration constant.
Availability
Available in iOS 2.0 and later.
http://developer.apple.com/library/ios/#documentation/uikit/reference/UINavigationController_Class/Reference/Reference.html
Related
Is this possible? How might I accomplish this?
Not possible according to Apple Docs.
The word possible may need an asterisk by it. It certainly looks like Apple didn't envision (or want) you doing this. But, depending on what your requirements are, there may be a workaround.
Disclaimer: this is kind of a hack. I'm not claiming this to be a good UI, just trying to show Eli what's possible.
I built an example, starting with the Xcode template for building a Tabbed Application. It has two view controllers: FirstViewController and SecondViewController. I decided to make FirstViewController the landscape-only view. In Interface Builder (Xcode UI design mode), I set the orientation of the FirstViewController's view to landscape, and made its size 480 wide by 251 high (I'm assuming iPhone/iPod here).
Solution
Now, what seems to be necessary is to have all the tab bar's view controllers claim to support autorotation to portrait and landscape. For example:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
So, both my view controllers have that same code. However, what I do in FirstViewController is to also override willAnimateToInterfaceOrientation:duration: and essentially undo what the UIViewController infrastructure does, just for this one landscape-only view controller.
FirstViewController.m:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
[super willAnimateRotationToInterfaceOrientation:interfaceOrientation duration:duration];
CGAffineTransform viewRotation;
CGRect frame;
if (UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
viewRotation = CGAffineTransformIdentity;
// TODO: change to dynamically account for status bar and tab bar height
frame = CGRectMake(0, 0, 480, 320 - 20 - 49);
} else {
viewRotation = CGAffineTransformMakeRotation(M_PI_2);
// TODO: change to dynamically account for status bar and tab bar height
frame = CGRectMake(0, 0, 320, 480 - 20 - 49);
}
// undo the rotation that UIViewController wants to do, for this view heirarchy
[UIView beginAnimations:#"unrotation" context: NULL];
[UIView setAnimationDuration: duration];
self.view.transform = viewRotation;
self.view.frame = frame;
[UIView commitAnimations];
}
What you get with this is that the tab bar will always rotate with the device. That's probably a requirement, to get your dual orientation views (e.g. SecondViewController) to do autorotation. But, the actual view content of FirstViewController now does not rotate. It stays in landscape orientation, no matter how the user turns the device. So, maybe that's partially good enough for you?
Also of note:
1) I changed the app's info plist file to set the initial orientation to landscape (since my FirstViewController was the landscape one):
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIInterfaceOrientation</key>
<string>UIInterfaceOrientationLandscapeRight</string>
2) In FirstViewController.xib, I set the main/parent UIView to not Autoresize Subviews. Depending on your view hierarchy, you may want to change this property in other child views, too. You can experiment with that setting.
Now, the available size for your landscape view does change a little bit, as the status bar and tab bar are rotated. So, you may need to adjust your layout a little bit. But, basically, you will still get a wide view for showing landscape content, no matter how the user holds their device.
Results
Watch Youtube demo of running app
Not possible according to Apple Docs. All UIViewControllers must support the same orientations for any to be rotatable.
See this document (scroll down to the section titled "Tab Bar Controllers and Rotation":
http://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/TabBarControllers.html#//apple_ref/doc/uid/TP40011313-CH3-SW1
I have my app 98% complete but stumbled across an issue which is stumping me!
Any help would be great.
Basically....I have a 5 tab controller. I have done some remodelling on the first tab view so that moving it from portrait to landscape moves everything around so that it looks great.
The other 4 tabs also move from portrait to landscape and back with ease.
Now....the issue I stumbled across was that if I had say tab 5 in portrait, moved it to landscape and then tapped tab 1, the only bits in tab 1 that orientate to landscape are the bits i fixed in the sizing inspector.
The bits I've re-positioned in code won't landscape.
If I however turn that tab 1 portrait and then back to landscape, it works!
The label fields I've moved with code using .frame and CGRectMake is in the
-(void)willAnimateRotationToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation duration: (NSTimeInterval)duration
method.
.......do I need to put some code in the AppDelegate which the TabController resides in??
It makes sense to me that the TabBarController knows the orientation.
When you tap a tab.....what method gets actioned first before the view loads??
I think I need to catch the orientation then, adjust my label positions and then load the view.......
I would appreciate any thoughts?
Gaz.
EDIT: It's like what I want to do is be able to change things in tab 1 if the orientation changes in other tabs.can you do that? It seems that the 5 tab views are separate...
Try making a call to your rotation method in your view controllers viewWillAppear method, checking for correct orientation, and moving what you need to move. Should look something like this
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self autoRotate];
}
I think that if you move something in code it maintains it's position, so you need to change it's position every time the orientation changes in every view controller where you move objects in code. Like:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
if(UIInterfaceOrientationIsLandscape(toInterfaceOrientation)){
//change positions with animation using duration
}else if(UIInterfaceOrientationIsPortrait(toInterfaceOrientation)){
//change positions with animation using duration
}
}
I hope it helps.
I need a way to force the orientation back to portrait on rotate.
The problem is that I have a Tab bar controller, but only want one of the tabs to autorotate.
So I have allowed rotation on all tabs and now I need a way to intercept a rotation on a tab where I don't want to allow rotation.
I'm guessing I can do this on - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration?
Thanks
Tom
There is no way to force rotation. In case of a UITabBarController you are out of luck. It is an all or nothing situation wrt interface rotation. If one of your tabs cannot rotate then the whole UITabBarController stays in portrait mode fixed.
Maybe this would even work if implemented in all your viewControllers:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return (return ([[self.tabBarController.selectedViewController class] isSubclassOfClass:[TurnableViewController class]]) || UIInterfaceOrientationIsPortrait(toInterfaceOrientation);
}
This will, however, not turn back your view if you switch back to a non-turnable, until you tilt your device...
Is there any good way to set UIImagePicker to landscape orientation? I tried to call setStatusBarOrientation after presentModalViewController like following,
[self presentModalViewController:picker animated:YES];
[[UIApplication sharedApplication] setStatusBarOrientation: UIInterfaceOrientationLandscapeRight animated: NO ];
However, the video duration view (00:00) on the right corner didn't rotate as orientation changed.
Unfortunately, according to the documentation, the image picker only supports portrait mode.
Apple doesn't support landscape for camera view. If you want anything shown as customised then add overlay view on it & transform that view to look as landscape view.
#Note:-overlay is also add as portrait thats why transformation needed.
I was able to create landscape camera view using the following code:
[self.view insertSubview:self.imagePicker.view atIndex:0];
self.imagePicker.view.transform =
CGAffineTransformScale (
CGAffineTransformTranslate(
CGAffineTransformMakeRotation(M_PI/2), -90, -15),
1.2,1.2)
;
Note that I am directly adding the imagePicker's view to the current view, not calling it as a modal.
self.imagePicker.view.transform seems to respond as expected to all CGAffineTransform function calls, though I can't speak to whether or not Apple approves of this method.
I have two apps, both of which force the user to use the iPhone in landscape mode, in order to have a wider screen, instead of a taller one.
One of the things I have found is that my first view will look fine, but all other views come up with their subviews (UIButtons, UIPicker, UIViews) squeezed to one side or clipped (depending on whether the elements were set to move, resize or stay in the same position as the view size changed). All my views are designed in IB in the landscape orientation. My underlying UIWindow, and everything I can think of has been laid out in landscape orientation. Even my plist file has the UIInterfaceOrientationLandscapeRight flag set.
Now, if I load all my views at the same time as my rootview controller, then I have no problems. But if I have views loaded later, they get clipped or squeezed.
The only way to get around the problem was to add the following line in my code that flips in a new view:
[coming.view setFrame:CGRectMake(0, 0, 480, 300)];
Anyone know why I need to do this? Is it just that the iPhone assumes that loaded views are 300x480 unless a transform gets applied to them?
Thanks.
ps. This is what the view looks like if I don't call setFrame, as described above:
alt text http://files.me.com/mahboud/ljhvun
All viewcontrollers that get loaded after the first one will have their screen similarly squeezed down. For some reason the first viewcontroller doesn't have this issue.
I think you want to use landscape mode in each single view in your app. And you want the nib to be landscape mode too. You can resize the view to (0,0,480,300 for statusbar, 320 for non-statusbar) in nib. And design what you want. Finally, in view controller return no for autorotate. And finally transform the view and rotate.
I had a similar problem, asked the question on SO, and then figured it out and answered it myself. You may want to check it out.
A proper answer will depend on knowing how you are forcing landscape orientation. If you are doing this through UIViewController and company, it should be relatively simple; for other methods probably more complex.
In the simple case, you should be able to override shouldAutorotateToInterfaceOrientation: on your view controller, setup your views in Interface Builder, and set the UIInterfaceOrientation key to UIInterfaceOrientationLandscapeRight in your Info.plist and be set.
A simple way I fixed this was to have my root view controller subclass UINavigationController, and implement shouldAutorotateToInterfaceOrientation to handle landscape view ie,
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return ((interfaceOrientation == UIInterfaceOrientationLandscapeLeft) ||
(interfaceOrientation == UIInterfaceOrientationLandscapeRight));
}
Every view controller that is pushed to the navigation controller seems to appear in landscape too.