Is there a documented way to set the iPhone orientation?
Asked Answered
D

17

96

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.

Dahlgren answered 8/10, 2008 at 8:6 Comment(3)
I've filed a bug on this, suggesting Apple either expose the above API, or honor the YES, NO being returned from the shouldRotate method, when a view first loads, not just when the phone rotates.Complicacy
I have filed a bug asking that setOrientation be exposedCerate
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait]; This method is deprecated and doesn't exists any more.Followup
J
6

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.

Jetliner answered 5/12, 2009 at 0:3 Comment(4)
It is not the case of iPhone Simulator 3.3.2. It is still not working.Minimal
@user How do you request an orientation when you push a view on the stack?Sideboard
You can get around Apple's static analysis. I successfully use undocumented code by using performSelector and relying on Obj-C's awesome dynamism.Orit
@KennethBallenegger I remember reading in the guidelines and/or agreement that hiding stuff like that can get your app removed and perhaps even your account removed. I wouldn't risk that just to use undocumented API.Microbe
K
101

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.

Kendricks answered 6/2, 2011 at 18:56 Comment(9)
Awesome, this one is not marked as the correct answer of this post, but this solved one half of my problem stackoverflow.com/questions/5030315. The model view trick 'forces' portrait orientation, would you also know one to force landscape?Iaverne
Hah hah!! this is just too stupid! BUT IT WORKS! - of course it does! :DBulky
Awesome, been struggling with this for a long time. Thanks! mateForefend
This is not a reliable solution on the long run. You will have issue with the responder chain, as some elements will resign their responder status.Formulary
To elaborate further, you need to check if it's in landscape mode first: if (self.view.frame.size.width == 320) [self performSelector:@selector(forceOrientation) withObject: self afterDelay:0];Nymph
This answer stopped working for me when compiling against the iOS 6 SDK. Beware that if you do this, it will not succeed in forcing the orientation (and viewDidAppear might not be called despite the user being able to interact with it).Reprobative
This seems to work for both iOS5 and iOS6: [self presentViewController:[UIViewController new] animated:NO completion:^{ [self dismissViewControllerAnimated:NO completion:nil]; }];Mangum
This appears to work on iOS 7 and iOS 8, but there is a noticeable black screen which flickers into view (iOS 8 mainly).Brozak
this will work for me only halt. if i go back from this screen my screen will be black.. any idea?Belcher
E
16

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.

Easterling answered 21/1, 2009 at 4:7 Comment(1)
Instead of self.view you should rotate self.window.view. This is handy if you have UITabBar or other controllers on top of your view.Ursula
P
16

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];
}
Pantaloon answered 27/4, 2009 at 12:32 Comment(2)
Will it also work if i added a UIAlertView ? I tried CGAffineTransformMakeRotation the view get rotated but the UIAlertView still according to status bar orientation i.e., -degreeToRadian(angle) ? Did you get something like this ?Moshe
The easiest way to do this with a UIAlertView is to set your view controller as the alertView's delegate and then on the: - (void) didPresentAlertView:(UIAlertView *) alertView, you perform the rotation based on the current [UIApplication sharedApplication].statusBarOrientation. If you don't animate it, shouldn't look too weird.Pantaloon
C
7

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.

Cran answered 19/8, 2010 at 21:24 Comment(2)
Hi, I just implemented your method to force a VC in the correct orientation before pushing it, but I have found that it only works when forcing Portrait orientations (if the current view is Portrait and you wish to force the next into landscape, it won't call shouldAutorotateToInterfaceOrientation: when popping).Have you found a workaround to force a view to Landscape ?Biogeochemistry
Interesting - I haven't had this issue, sorry. I would try playing around with the sequence of pushes and pops, there's a decent chance you'll come up with something.Cran
B
7

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

Biogeochemistry answered 27/10, 2010 at 15:54 Comment(1)
This solution worked best for me. Importantly, the fix which presents an empty modalViewController causes incorrect animation of pushing viewcontrollers onto the stack. This UIWindow method doesn't suffer the same problem.Faery
B
7

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);
}
Brennan answered 9/5, 2012 at 12:0 Comment(3)
For me it does not work like a charm: The navigation bar and tool bar shrinks, to the height that they would have in Landscape view. The method with removing and re-adding the topmost view keeps the correct height, but has a disadvantage too: My navigation bar moves down a bit (like as if thetering bar is enabled)Crate
It works on my end, both on modal and pushed viewcontroller. But only on iOS 5. :(Brennan
Yes I got it to work in ios5, too. Now in ios6 the height of navigation bar shrinks once the modal landscape view controller is dismissedCrate
J
6

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.

Jetliner answered 5/12, 2009 at 0:3 Comment(4)
It is not the case of iPhone Simulator 3.3.2. It is still not working.Minimal
@user How do you request an orientation when you push a view on the stack?Sideboard
You can get around Apple's static analysis. I successfully use undocumented code by using performSelector and relying on Obj-C's awesome dynamism.Orit
@KennethBallenegger I remember reading in the guidelines and/or agreement that hiding stuff like that can get your app removed and perhaps even your account removed. I wouldn't risk that just to use undocumented API.Microbe
W
5

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.

Walcott answered 13/6, 2011 at 15:20 Comment(0)
S
3

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 ];
Sd answered 7/12, 2010 at 7:11 Comment(4)
This is the only one of the answers on this page that I've been able to successfully adapt to my navigation controller code. The one minor problem is that the rotated navigation bar does not change height between portrait and landscape orientations as it would if rotated via the normal mechanism.Hallway
@DanDyer have you solved the issue with the wrong height of the naviagtion bar? (Especially in ios6 the height is wrong)Crate
@Crate I eventually managed to convince the client that we shouldn't be doing this. For iOS 6 you may need to do something with the new shouldAutorotate method (see https://mcmap.net/q/162816/-setstatusbarorientation-animated-not-working-in-ios-6).Hallway
@DanDyer solved it yesterday, a simple hide and show of navigation and statusBar in ViewDidAppear solved it.Now all works (SDK ios6, target ios5) I have to retest with target ios6Crate
C
2

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 ];
}
Cover answered 18/4, 2011 at 16:26 Comment(0)
L
1

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.

Latvian answered 3/10, 2011 at 15:45 Comment(0)
N
1

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.

Nymph answered 5/7, 2012 at 3:55 Comment(0)
C
1

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.

Crawler answered 13/1, 2013 at 0:42 Comment(0)
A
0

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).

Audio answered 10/9, 2010 at 14:26 Comment(0)
A
-3

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

Aftmost answered 8/10, 2008 at 15:21 Comment(1)
That stops rotation, what I am looking for is to force a rotation back to portrait if the user is in landscape. I have added an example above to say what I mean better.Dahlgren
W
-3

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.

Woodrow answered 8/10, 2008 at 20:9 Comment(1)
It is possible with the code I originally posted, you just never know when that might stop working ;)Dahlgren
I
-3

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];
Isreal answered 10/10, 2008 at 6:58 Comment(2)
I've found this is enough to make it work in 4.1 as well. However because my status bar is set to hidden I gain 20pixel gap at the top :(Rant
setOrientation is private api and the app using it is rejected.Downtrodden

© 2022 - 2024 — McMap. All rights reserved.