I'm having a real issue with UITabBarController.
The outcome I'm after is the following:
1) in portrait mode, a simple tab bar based application (with navigation bars) nothing too fancy.
2) in landscape mode, I want to use my own UIViewController ignoring the UITabBar completely.
The approach (I tried many variants) I tried last which I fail to understand why is not "working" is the following:
I have a custom UIViewController (Call this AA) that is suppose to manage "everything".
This controller is added to the window in application start and in its loadView creates two controllers: a UITabBarController (Call this TBC) and a UILandscapeController (Call this LSC). then I add the tabbarcontroller view as a subview of AA's view.
now in AA class I override the didRotate blah or willRotate blah and basically want to switch between the two views, by this I means something like: (pseudo code):
going from portrait to landscape:
[TBC.view removeFromSuperView];
[AA.view addSubview:LSC.view];
and when returning to portrait reverse it.
[LSC.view removeFromSuperView];
[AA.view addSubview:TBC.view];
The amount of problems I have (well, it simple rotates wrongly creating a real messed up interface) are something completely unexplained. It seems like the tabbarcontroller view does not "like" at all to be in the standard view heirarchy but rather it wants to be attached directly to the screen.
I wonder what is the best approach to achieve my goal and why the tabbar does not like to be a subview of a view,
any hints mostly appreciated.
-t
Just in case you still need the answer, or someone else stumbles onto this, I've done the same thing and got it working, but there are a couple of hoops you have to jump through. In order to rotate a UITabBarController's view, there are four things you have to do:
Remove the status bar before switching to the view
Rotate the view to the new frame
Add the status bar back to the view
Switch to the view.
I've got a RootRotationController that does this that looks like this:
#implementation RootRotationController
#define degreesToRadian(x) (M_PI * (x) / 180.0)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if ((UIInterfaceOrientationPortrait == interfaceOrientation) || (UIInterfaceOrientationPortraitUpsideDown == interfaceOrientation)) {
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
}
// Return YES for supported orientations
return YES;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
[super willAnimateRotationToInterfaceOrientation:interfaceOrientation duration:duration];
if (UIInterfaceOrientationLandscapeLeft == interfaceOrientation) {
self.view = self.landscape.view;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(-90));
self.view.bounds = CGRectMake(0, 0, 480, 300);
} else if (UIInterfaceOrientationLandscapeRight == interfaceOrientation) {
self.view = self.landscape.view;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
self.view.bounds = CGRectMake(0, 0, 480, 300);
} else if (UIInterfaceOrientationPortrait == interfaceOrientation) {
mainInterface.view.transform = CGAffineTransformIdentity;
mainInterface.view.transform = CGAffineTransformMakeRotation(degreesToRadian(0));
mainInterface.view.bounds = CGRectMake(0, 0, 300, 480);
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO];
self.view = mainInterface.view;
} else if (UIInterfaceOrientationPortraitUpsideDown == interfaceOrientation) {
mainInterface.view.transform = CGAffineTransformIdentity;
mainInterface.view.transform = CGAffineTransformMakeRotation(degreesToRadian(180));
mainInterface.view.bounds = CGRectMake(0, 0, 300,480);
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO];
self.view = mainInterface.view;
}
}
In addition, you should know that shouldAutorotateToInterfaceOrientation is called just after adding the root controller's view to the window, so you'll have to re-enable the status bar just after having done so in your application delegate.
Your problem comes from the typo, I think. Change removeFromSuperView to removeFromSuperview.
Though, it still has a problem. Tab bar doesn't rotate properly. It go upwards till it disappers.
How about not removing the tab bar, and make it transparent.
Check out the UIViewController instance method rotatingFooterView in the docs.
Or, you may manage TabBar by yourself, not through the UITabBarController.
Related
iOS Developers will surely knows about the issue about status bar and the famous "slide/hamburger/drawer". The issue is well explained here: http://uxmag.com/articles/adapting-ui-to-ios-7-the-side-menu
I'm using MMDrawerController library and it has a nice hack that lets us to create a dummy status bar just above the container view controller. Unfortunately this doesn't work really good. What's the news? The news is that I stumbled upon an app (Tinder) that perfectly solve this mind blowing issue. I've created a gif that perfectly shows what Tinder does.
You need to wait a few seconds for seeing the gif because there's a bug in it and I don't know how to get rid of. Just wait one/two seconds and you will able to see the gif correctly.
Anyway, what Tinder does? When the user taps on the top left menu button and begin to swipe right the status bar fades out neatly. And when the view is revert to the original position the status bar will show up again.
I am both happy and a bit sad for this because this means that a way must be to do it but I really don't know how to implement it (perhaps hacking MMDrawerController). Any help will be so much appreciated.
IMPORTANT
Please pay attention to the fact that the method setStatusBarHidden: will completely hide the status bar, this means that the entire view is with a height -20px. This is obviously not the solution because as you can see from the gif the view is not stretched.
Your main problem is with MMDrawerController. If you'll digg into it you'll find a lot of methods statusbar related such as setShowsStatusBarBackgroundView setStatusBarViewBackgroundColor and more. Something in their code pushes the view up when the statusbar is hidden.
Alternatively you can use another drawer controller or use custom code.
Here's a simple way how to accomplishe this:
ViewControllerA:
-(BOOL)prefersStatusBarHidden
{
return _hidden;
}
- (void)statusHide
{
[UIView animateWithDuration:0.4 animations:^() {[self setNeedsStatusBarAppearanceUpdate];
}completion:^(BOOL finished){}];
}
ViewControllerB: (Container in ViewControllerA)
- (IBAction)move:(UIButton *)sender
{
parent = (ViewController*)self.parentViewController;
parent.hidden = !parent.hidden;
CGRect frame = parent.blueContainer.frame;
if(parent.hidden)
{
frame.origin.x = 150;
}
else
{
frame.origin.x = 0;
}
[UIView animateWithDuration:1 animations:^() {parent.blueContainer.frame = frame;}completion:^(BOOL finished){}];
[parent statusHide];
}
For iOS 6 compatieblty use:
[[UIApplication sharedApplication] setStatusBarHidden:_hidden withAnimation:UIStatusBarAnimationFade];
The table view and other subviews will stay in their location and won't be pushed up.
Edit:
Adding a NavigationBar:
UINavigationController will alter the height of its UINavigationBar to
either 44 points or 64 points, depending on a rather strange and
undocumented set of constraints. If the UINavigationController detects
that the top of its view’s frame is visually contiguous with its
UIWindow’s top, then it draws its navigation bar with a height of 64
points. If its view’s top is not contiguous with the UIWindow’s top
(even if off by only one point), then it draws its navigation bar in
the “traditional” way with a height of 44 points. This logic is
performed by UINavigationController even if it is several children
down inside the view controller hierarchy of your application. There
is no way to prevent this behavior.
Taken from here
You could very simply subclass UINavigationController and create your own navbar to avoid this annoyness.
i don't know if it will sove your problem but i got almost the same effect using the SWRevealViewController project. In the appDelegate I've set the delegate method from this class to do this:
- (void)revealController:(SWRevealViewController *)revealController willMoveToPosition:(FrontViewPosition)position {
#ifdef DEBUG
NSArray *teste = #[#"FrontViewPositionLeftSideMostRemoved",#"FrontViewPositionLeftSideMost",#"FrontViewPositionLeftSide",#"FrontViewPositionLeft",#"FrontViewPositionRight",#"FrontViewPositionRightMost",#"FrontViewPositionRightMostRemoved"];
NSLog(#"%# %d", teste[position], position);
#endif
if (position == FrontViewPositionRight)
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];
UINavigationController *frontViewController = (id)revealController.frontViewController;
frontViewController.navigationBar.centerY += (position == FrontViewPositionRight) ? 20 : 0; // 20 == statusbar heihgt
}
- (void)revealController:(SWRevealViewController *)revealController didMoveToPosition:(FrontViewPosition)position {
if (position == FrontViewPositionLeft)
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
}
centerY is a category in the UIView which sets the center.y without dealing the boring part of setting frame variables.
Here is how you should do that in iOS 7:
#implementation ViewController
{
BOOL _hideStatusBar;
}
-(UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleDefault;
}
-(UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
return UIStatusBarAnimationFade;
}
-(BOOL)prefersStatusBarHidden
{
return _hideStatusBar;
}
-(void)setStatusBarHidden:(BOOL)hidden
{
[UIView animateWithDuration:1.0 animations:^{
_hideStatusBar = hidden;
[self setNeedsStatusBarAppearanceUpdate];
}];
}
#end
Check out the method setStatusBarHidden:withAnimation: on UIApplication. It will allow you to show or hide the status bar and the animation can be none, fade, or slide. You just need to add a call to hide the bar and one to show the bar at the correct times and decide if you like the fade as you illustrated or if the slide works better for you.
https://developer.apple.com/library/ios/DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html#//apple_ref/occ/instm/UIApplication/setStatusBarHidden:withAnimation:
You can used -setStatusBarHidden:withAnimation: if you adjust your views frame in -viewDidAppear:, then you will not see any stretch.
Note that autolayout is disabled.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CGRect frame = self.view.frame;
// adjust root view frame
frame.origin.y -= 20;
frame.size.height += 20;
[self.view setFrame:frame];
// adjust subviews y position
for (UIView *subview in [self.view subviews])
{
CGRect frame = subview.frame;
frame.origin.y += 20;
[subview setFrame:frame];
}
}
- (IBAction)sliderChanged:(id)sender
{
UISlider *s = (UISlider *)sender;
if (s.value > .5)
{
UIApplication *app = [UIApplication sharedApplication];
if (![app isStatusBarHidden])
[app setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];
}
else
{
UIApplication *app = [UIApplication sharedApplication];
if ([app isStatusBarHidden])
[app setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
}
}
I noticed that after an orientation change from portrait to landscape, I'm not getting touchesBegan events for some parts of my view any longer. I suppose that this is because I'm not informing my UIView about the dimension change of my window's frame after the device rotation.
I'm setting up everything programmatically (UIWindow, UIViewController, UIView) like this:
myViewController = [[myUIViewController alloc] init];
myWindow = [[UIWindow alloc] initWithFrame: rect];
myView = [[myUIView alloc] initWithFrame: [myWindow bounds]];
[myViewController setView:myView];
[myWindow addSubview:myView];
[myWindow setFrame:rect];
[myWindow makeKeyAndVisible];
When I get the didRotateFromInterfaceOrientation notification, I'm updating the window frame like this:
[[[self view] window] setFrame:rect];
But after that, my UIView does no longer get touchesXXX events for all areas. It seems that only the areas of the previous frame are still reporting events. So my question: Is there anything else I need to do in didRotateFromInterfaceOrientation to inform my UIView about the dimension change?
Thanks for help!
EDIT: Do I have to reposition the UIView on didRotateFromInterfaceOrientation() or is this done automatically? I noticed that the "transform" property of my UIView is set to a transformation matrix when the orientation changes. However, this makes it very hard to reposition my view. The docs say that the "frame" property can't be used when a transformation is active, so I tried to modify the "center" property to reposition my view, but this also doesn't work correctly. I want to move the view to the top-left corner, so I set "center" to (screenwidth/2,screenheight/2) but it doesn't position the view correctly :( Any idea or info what must be done to get the events right in orientation mode?
I have had this same problem, and I believe it is a frame issue.
I had to manually set the rects depending on orientation, as well as set the userInteractionEnabled on the main view, like:
if (appOrientation == 0)
appOrientation = [UIApplication sharedApplication].statusBarOrientation;
if (appOrientation == UIInterfaceOrientationLandscapeLeft || appOrientation == UIInterfaceOrientationLandscapeRight) {
[[UIApplication sharedApplication] setStatusBarHidden:YES];
myView.frame = CGRectMake(0, 0, 1024, 768);
} else {
[[UIApplication sharedApplication] setStatusBarHidden:YES];
myView.frame = CGRectMake(0, 0, 768, 1024);
}
myView.userInteractionEnabled = YES;
OK I know this is an old question but this is how I fixed this issue.
In my situation I had a storyboard with a view that would be displayed either in portrait or forced to landscape mode depending on a user setting.
My app displays the statusBar at all times except for when I'm showing this view.
To make this all work, the transformations had to be applied in the viewWillAppear method for one. I had the following code in the viewDidAppear method at first and that messed with the bounds for the touchesBegan event I guess.
Here's the viewWillAppear code:
- (void)viewWillAppear:(BOOL)animated
{
// Hide the status bar
[[UIApplication sharedApplication] setStatusBarHidden:YES];
// This is to offset the frame so that the view will take the fullscreen once the status bar is hidden
self.navigationController.navigationBar.frame = CGRectOffset(self.navigationController.navigationBar.frame, 0.0, -20.0);
// If view is to be displayed in landscape mode
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"orientation"])
{
// Change status bar orientation
[[UIApplication sharedApplication] setStatusBarOrientation:UIDeviceOrientationLandscapeLeft];
// Turn the view 90 degrees
[self.navigationController.view setTransform:CGAffineTransformMakeRotation(M_PI/2)];
// Set the frame and the bounds for the view
// Please Note: Frame size has to be reversed for some reason.
[self.navigationController.view setFrame: CGRectMake(0,0,320,480)];
[self.navigationController.view setBounds: CGRectMake(0,0,480,320)];
// Make sure user can interact with view
[self.navigationController.view setUserInteractionEnabled:YES];
}
}
Any other thing that had to happen layout wise, have to happen in the viewDidAppear method. For instance I had an image that covered the whole view and the frame had to be set depending on the orientation. Setting the frame in the viewWillAppear gave weird results but the same code worked perfectly in viewDidAppear.
Hope this helps someone as I banged my head for 6 hours on this thing.
When in landscape, transitioning from one view (that's part of a Navigation Controller stack) to another as a modal view, with UIModalTransitionStyleFlipHorizontal set as the modalTransitionStyle, the view flips vertically in landscape mode.
Everything else about the look of the views is fine after the animation, though I did notice that the frame size of the views isn't changing which is causing issues in other places of my code as well. I figured if I fix whatever is making this particular flip vertical instead of horizontal, it will fix the other issue.
I assume it has something to do with the window itself not changing orientation, but I'm not sure that's it.
Anyone have any ideas?
Talked to an Apple Engineer at WWDC and figured out the UIModalTransitionStyleFlipHorizontal does not work in landscape, it will flip what looks like in vertical.
The other issue I mentioned was because I wasn't adapting a frame to the view correctly.
There is a solution for this if you use iOS7 custom view controller transition. The ViewController which initiates the transition should confirm to protocols an implement the following methods.
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController: (UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return (id<UIViewControllerAnimatedTransitioning>)self;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
return (id<UIViewControllerAnimatedTransitioning>)self;
}
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return 0.7f;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIView *containerView = [transitionContext containerView];
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[containerView addSubview:fromVC.view];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[containerView addSubview:toVC.view];
UIViewAnimationOptions animationOption;
if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) {
animationOption = ([toVC.presentedViewController isEqual:fromVC])?UIViewAnimationOptionTransitionFlipFromTop:UIViewAnimationOptionTransitionFlipFromBottom;
}
else {
animationOption = ([toVC.presentedViewController isEqual:fromVC])?UIViewAnimationOptionTransitionFlipFromLeft:UIViewAnimationOptionTransitionFlipFromRight;
}
[UIView transitionFromView:fromVC.view
toView:toVC.view
duration:[self transitionDuration:transitionContext]
options:animationOption
completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
The transitioning delegate of the modal ViewController to be displayed should be set like this:
[modalViewController setTransitioningDelegate:self];
For example this linke can be put in the prepareForSegue: method.
That's it.
I've noticed that I'm getting very intermittent orientation on my device & the simulator.
I have a modal view controller that I present, and that is the only thing in my app which supports rotation.
If I launch the app in portrait without moving the device, open the modal VC and then rotate the device, it usually works. However sometimes if I open the app holding the device in landscape, then rotate to portrait, launch the VC and then rotate the device, no rotation occurs. It seems very intermittent. Sometimes if I launch the app in portrait mode and then open the VC and rotate the device, nothing happens, and until I quit and relaunch it no orientation occurs in the app.
It's strange because 50% of the time it works! Whenever I launch it through Xcode and set breakpoints in shouldAutorotateToInterfaceOrientation it always works!
Anyone ever had this or know what's going on?
Since as you mentioned "intermittent", i would say it has something to do with what are you doing after the rotation.
To find the bug, i suggest removing any code after the rotation happens. Comment out any network activities or OpenGL operations. Could also help to close XCode and reopen it.
If nothing helps, i would make a new project and start moving the files one by one and test.
I've had similar challenges getting autorotation to work properly for view controllers whose parent view controllers don't autorotate, although most of my experience has related to wrangling UINavigationController as opposed to modal view controllers. Still, I'd recommend trying the following:
Call presentModalViewController: on the top level view controller in your hierarchy rather than a deeper viewController.
If that doesn't solve it, try subclassing your top level view controller, and overriding its shouldAutorotateToInterfaceOrientation: to look something like this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if (self.modalViewController) {
return [self.modalViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
return [super shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
Does calling
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
when you show your rotation-aware view help? (and possibly turning it off again when you dismiss it?)
The other thing I would look for is suspicious calls to [UIWindow makeKeyWindow] or [UIResponder becomeFirstResponder].
I'd also sign up for UIDeviceOrientationDidChangeNotification events and make sure that the Modal view got a call to [UIViewController shouldAutorotateToInterfaceOrientation:] for each event you receive.
I've finally discovered what the problem was!
It turns out that only the very first UIView you add to your UIWindow will get asked whether or not to rotate. Any subsequent views don't receive this message. My app was using another view to animate away from the Default.png that is displayed on launch, and this was added to the window first!
Even if you try to launch your application in landscape, your phone will launch it in portrait and then go into landscape depending in which position your phone is.
I don't know what code you are using to accomplish this. Below is one which is taken from Apple code snippets. See if this can help.
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)
interfaceOrientation duration:(NSTimeInterval)duration {
if (interfaceOrientation == UIInterfaceOrientationPortrait)
{
self.view = self.portrait;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(0));
self.view.bounds = CGRectMake(0.0, 0.0, 300.0, 480.0);
}
else if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
self.view = self.landscape;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(-90));
self.view.bounds = CGRectMake(0.0, 0.0, 460.0, 320.0);
}
else if (interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
self.view = self.portrait;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(180));
self.view.bounds = CGRectMake(0.0, 0.0, 300.0, 480.0);
}
else if (interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
self.view = self.landscape;
self.view.transform = CGAffineTransformIdentity;
self.view.transform =
CGAffineTransformMakeRotation(degreesToRadian(90));
self.view.bounds = CGRectMake(0.0, 0.0, 460.0, 320.0);
}
}
I have an app where I would like to support device rotation in certain views but other don't particularly make sense in Landscape mode, so as I swapping the views out I would like to force the rotation to be set to portrait.
There is an undocumented property setter on UIDevice that does the trick but obviously generates a compiler warning and could disappear with a future revision of the SDK.
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];
Are there any documented ways to force the orientation?
Update: I thought I would provide an example as I am not looking for shouldAutorotateToInterfaceOrientation as I have already implemented that.
I want my app to support landscape and portrait in View 1 but only portrait in View 2. I have already implemented shouldAutorotateToInterfaceOrientation for all views but if the user is in landscape mode in View 1 and then switches to View 2, I want to force the phone to rotate back to Portrait.
This is long after the fact, but just in case anybody comes along who isn't using a navigation controller and/or doesn't wish to use undocumented methods:
UIViewController *c = [[UIViewController alloc]init];
[self presentModalViewController:c animated:NO];
[self dismissModalViewControllerAnimated:NO];
[c release];
It is sufficient to present and dismiss a vanilla view controller.
Obviously you'll still need to confirm or deny the orientation in your override of shouldAutorotateToInterfaceOrientation. But this will cause shouldAutorotate... to be called again by the system.
If you want to force it to rotate from portrait to landscape here is the code. Just note that you need adjust the center of your view. I noticed that mine didn't place the view in the right place. Otherwise, it worked perfectly. Thanks for the tip.
if(UIInterfaceOrientationIsLandscape(self.interfaceOrientation)){
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
self.view.bounds = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
self.view.center = CGPointMake(160.0f, 240.0f);
[UIView commitAnimations];
}
From what I can tell, the setOrientation: method doesn't work (or perhaps works no longer). Here's what I'm doing to do this:
first, put this define at the top of your file, right under your #imports:
#define degreesToRadian(x) (M_PI * (x) / 180.0)
then, in the viewWillAppear: method
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
if (self.interfaceOrientation == UIInterfaceOrientationPortrait) {
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
self.view.bounds = CGRectMake(0.0, 0.0, 480, 320);
}
if you want that to be animated, then you can wrap the whole thing in an animation block, like so:
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration:1.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
if (self.interfaceOrientation == UIInterfaceOrientationPortrait) {
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
self.view.bounds = CGRectMake(0.0, 0.0, 480, 320);
}
[UIView commitAnimations];
Then, in your portrait mode controller, you can do the reverse - check to see if its currently in landscape, and if so, rotate it back to Portrait.
I was having an issue where I had a UIViewController on the screen, in a UINavigationController, in landscape orientation. When the next view controller is pushed in the flow, however, I needed the device to return to portrait orientation.
What I noticed, was that the shouldAutorotateToInterfaceOrientation: method isn't called when a new view controller is pushed onto the stack, but it is called when a view controller is popped from the stack.
Taking advantage of this, I am using this snippet of code in one of my apps:
- (void)selectHostingAtIndex:(int)hostingIndex {
self.transitioning = YES;
UIViewController *garbageController = [[[UIViewController alloc] init] autorelease];
[self.navigationController pushViewController:garbageController animated:NO];
[self.navigationController popViewControllerAnimated:NO];
BBHostingController *hostingController = [[BBHostingController alloc] init];
hostingController.hosting = [self.hostings objectAtIndex:hostingIndex];
[self.navigationController pushViewController:hostingController animated:YES];
[hostingController release];
self.transitioning = NO;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
if (self.transitioning)
return (toInterfaceOrientation == UIInterfaceOrientationPortrait);
else
return YES;
}
Basically, by creating an empty view controller, pushing it onto the stack, and immediately popping it off, it's possible to get the interface to revert to the portrait position. Once the controller has been popped, I just push on the controller that I intended to push in the first place. Visually, it looks great - the empty, arbitrary view controller is never seen by the user.
There is a simple way to programmatically force iPhone to the necessary orientation - using two of already provided answers by kdbdallas, Josh :
//will rotate status bar
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
//will re-rotate view according to statusbar
UIViewController *c = [[UIViewController alloc]init];
[self presentModalViewController:c animated:NO];
[self dismissModalViewControllerAnimated:NO];
[c release];
works like a charm :)
EDIT:
for iOS 6 I need to add this function:
(works on modal viewcontroller)
- (NSUInteger)supportedInterfaceOrientations
{
return (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight);
}
I've been digging and digging looking for a good solution to this. Found this blog post that does the trick: remove your outermost view from the key UIWindow and add it again, the system will then re-query the shouldAutorotateToInterfaceOrientation: methods from your viewcontrollers, enforcing the correct orientation to be applied.
See it : iphone forcing uiview to reorientate
This is no longer an issue on the later iPhone 3.1.2 SDK. It now appears to honor the requested orientation of the view being pushed back onto the stack. That likely means that you would need to detect older iPhone OS versions and only apply the setOrientation when it is prior to the latest release.
It is not clear if Apple's static analysis will understand that you are working around the older SDK limitations. I personally have been told by Apple to remove the method call on my next update so I am not yet sure if having a hack for older devices will get through the approval process.
Josh's answer works fine for me.
However, I prefer posting an "orientation did change, please update UI" notification. When this notification is received by a view controller, it calls shouldAutorotateToInterfaceOrientation:, allowing you to set any orientation by returning YES for the orientation you want.
[[NSNotificationCenter defaultCenter] postNotificationName:UIDeviceOrientationDidChangeNotification object:nil];
The only problem is that this forces a re-orientation without an animation. You would need to wrap this line between beginAnimations: and commitAnimations to achieve a smooth transition.
Hope that helps.
FWIW, here's my implementation of manually setting orientation (to go in your app's root view controller, natch):
-(void)rotateInterfaceToOrientation:(UIDeviceOrientation)orientation{
CGRect bounds = [[ UIScreen mainScreen ] bounds ];
CGAffineTransform t;
CGFloat r = 0;
switch ( orientation ) {
case UIDeviceOrientationLandscapeRight:
r = -(M_PI / 2);
break;
case UIDeviceOrientationLandscapeLeft:
r = M_PI / 2;
break;
}
if( r != 0 ){
CGSize sz = bounds.size;
bounds.size.width = sz.height;
bounds.size.height = sz.width;
}
t = CGAffineTransformMakeRotation( r );
UIApplication *application = [ UIApplication sharedApplication ];
[ UIView beginAnimations:#"InterfaceOrientation" context: nil ];
[ UIView setAnimationDuration: [ application statusBarOrientationAnimationDuration ] ];
self.view.transform = t;
self.view.bounds = bounds;
[ UIView commitAnimations ];
[ application setStatusBarOrientation: orientation animated: YES ];
}
coupled with the following UINavigationControllerDelegate method (assuming you're using a UINavigationController):
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
// rotate interface, if we need to
UIDeviceOrientation orientation = [[ UIDevice currentDevice ] orientation ];
BOOL bViewControllerDoesSupportCurrentOrientation = [ viewController shouldAutorotateToInterfaceOrientation: orientation ];
if( !bViewControllerDoesSupportCurrentOrientation ){
[ self rotateInterfaceToOrientation: UIDeviceOrientationPortrait ];
}
}
That takes care of rotating the root view according to whether an incoming UIViewController supports the current device orientation. Finally, you'll want to hook up rotateInterfaceToOrientation to actual device orientation changes in order to mimic standard iOS functionality. Add this event handler to the same root view controller:
-(void)onUIDeviceOrientationDidChangeNotification:(NSNotification*)notification{
UIViewController *tvc = self.rootNavigationController.topViewController;
UIDeviceOrientation orientation = [[ UIDevice currentDevice ] orientation ];
// only switch if we need to (seem to get multiple notifications on device)
if( orientation != [[ UIApplication sharedApplication ] statusBarOrientation ] ){
if( [ tvc shouldAutorotateToInterfaceOrientation: orientation ] ){
[ self rotateInterfaceToOrientation: orientation ];
}
}
}
Finally, register for UIDeviceOrientationDidChangeNotification notifications in init or loadview like so:
[[ NSNotificationCenter defaultCenter ] addObserver: self
selector: #selector(onUIDeviceOrientationDidChangeNotification:)
name: UIDeviceOrientationDidChangeNotification
object: nil ];
[[ UIDevice currentDevice ] beginGeneratingDeviceOrientationNotifications ];
This works for me (thank you Henry Cooke):
The aim for me was to deal with landscape orientations changes only.
init method:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
- (void)orientationChanged:(NSNotification *)notification {
//[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
CGRect bounds = [[ UIScreen mainScreen ] bounds ];
CGAffineTransform t;
CGFloat r = 0;
switch ( orientation ) {
case UIDeviceOrientationLandscapeRight:
r = 0;
NSLog(#"Right");
break;
case UIDeviceOrientationLandscapeLeft:
r = M_PI;
NSLog(#"Left");
break;
default:return;
}
t = CGAffineTransformMakeRotation( r );
UIApplication *application = [ UIApplication sharedApplication ];
[ UIView beginAnimations:#"InterfaceOrientation" context: nil ];
[ UIView setAnimationDuration: [ application statusBarOrientationAnimationDuration ] ];
self.view.transform = t;
self.view.bounds = bounds;
[ UIView commitAnimations ];
[ application setStatusBarOrientation: orientation animated: YES ];
}
I have an app where I would like to support device rotation in certain views but other don't particularly make sense in Landscape mode, so as I swapping the views out I would like to force the rotation to be set to portrait.
I realise that the above original post in this thread is very old now, but I had a similar problem to it - ie. all of the screens in my App are portrait only, with the exception of one screen, which can be rotated between landscape and portrait by the user.
This was straightforward enough, but like other posts, I wanted the App to automatically return to portrait regardless of the current device orientation, when returning to the previous screen.
The solution I implemented was to hide the Navigation Bar while in landscape mode, meaning that the user can only return to previous screens whilst in portrait. Therefore, all other screens can only be in portrait.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)pInterfaceOrientation {
BOOL lHideNavBar = self.interfaceOrientation == UIInterfaceOrientationPortrait ? NO : YES;
[self.navigationController setNavigationBarHidden:lHideNavBar animated:YES];
}
This also has the added benefit for my App in that there is more screen space available in landscape mode. This is useful because the screen in question is used to display PDF files.
Hope this helps.
I solved this quite easily in the end. I tried every suggestion above and still came up short, so this was my solution:
In the ViewController that needs to remain Landscape (Left or Right), I listen for orientation changes:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didRotate:)
name:UIDeviceOrientationDidChangeNotification object:nil];
Then in didRotate:
- (void) didRotate:(NSNotification *)notification
{ if (orientationa == UIDeviceOrientationPortrait)
{
if (hasRotated == NO)
{
NSLog(#"Rotating to portait");
hasRotated = YES;
[UIView beginAnimations: #"" context:nil];
[UIView setAnimationDuration: 0];
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-90));
self.view.bounds = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
self.view.frame = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
[UIView commitAnimations];
}
}
else if (UIDeviceOrientationIsLandscape( orientationa))
{
if (hasRotated)
{
NSLog(#"Rotating to lands");
hasRotated = NO;
[UIView beginAnimations: #"" context:nil];
[UIView setAnimationDuration: 0];
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(0));
self.view.bounds = CGRectMake(0.0f, 0.0f, 320.0f, 480.0f);
self.view.frame = CGRectMake(0.0f, 0.0f, 320.0f, 480.0f);
[UIView commitAnimations];
}
}
Keep in mind any Super Views/Subviews that use autoresizing, as the view.bounds/frame are being reset explicitly...
The only caveat to this method for keeping the view Landscape, is the inherent animation switching between orientations that has to occur, when it would be better to have it appear to have no change.
iOS 6 solution:
[[[self window] rootViewController] presentViewController:[[UIViewController alloc] init] animated:NO completion:^{
[[[self window] rootViewController] dismissViewControllerAnimated:NO completion:nil];
}];
The exact code depends per app and also where you place it (I used it in my AppDelegate). Replace [[self window] rootViewController] with what you use. I was using a UITabBarController.
I found a solution and wrote something in french (but code are in english). here
The way is to add the controller to the window view (the controller must possess a good implementation of the shouldRotate.... function).
If you are using UIViewControllers, there is this method:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
Return NO for the view controllers containing the views you don't want to rotate.
More info here
I don't think this is possible to do at run-time, though you could of course just apply a 90 degree transform to your UI.
This is what I use. (You get some compile warnings but it works in both the Simulator and the iPhone)
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];