How do I programmatically set device orientation in iOS 7?
Asked Answered
N

18

76

I am working on an iPad app, using AutoLayout, where if the user enables a certain mode ("heads-up" mode), I want to support only portrait (or portrait upside down) orientation, and furthermore, if the device is in landscape, I'd like to automatically switch to portrait mode.

In the top view controller, I have the following:

- (NSUInteger) supportedInterfaceOrientations {
    if (self.modeHeadsUp) {
        return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
    } else {
        return UIInterfaceOrientationMaskAll;
    }
}

- (BOOL) shouldAutorotate {
    return TRUE;
}

Based on answers I've seen elsewhere here, the answer seems to be that I should use "application setStatusBarOrientation". Therefore, in the method where the user has selected "heads-up" mode, I have included:

    UIApplication *application = [UIApplication sharedApplication];
    [application setStatusBarOrientation:UIInterfaceOrientationPortrait
                                animated:YES];

However, this simply doesn't seem to do anything. While I can physically move the device to get it to rotate into portrait, it doesn't do so automatically.

In fact, when in landscape mode after running the above code to attempt to programmatically set the orientation, when I query the application "statusBarOrientation" with the following code, it remains at "4" for landscape:

UIApplication *application = [UIApplication sharedApplication];
int orientation = [application statusBarOrientation];
self.movesTextView.text = [NSString stringWithFormat:@"ORIENTATION %d", orientation];

It seemed like maybe autolayout wasn't being triggered with the setStatusBarOrientation, so I attempted to add this code after, to no effect:

    [super updateViewConstraints];
    [self.view updateConstraints];

I realize Apple wants to leave device orientation in the hands of the user. However, I'd like to be able to support landscape mode when not in "heads-up" mode.

Am I missing something to be able to force orientation change?

Naima answered 8/1, 2014 at 4:47 Comment(0)
D
201

For iOS 7 & 8:

Objective-C:

NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];
[[UIDevice currentDevice] setValue:value forKey:@"orientation"];

Swift 3+:

let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")

I call it in - viewDidAppear:.

Doubletime answered 8/1, 2014 at 4:52 Comment(14)
This works! Now, I'm concerned that this generates a complier error, and people seem to frown mightily upon objc_msgSend (see #17263854), but this works! I'm going to try and see if there is a way to do this more cleanly.Naima
Alas, this seems to be an end-run around the now-deprecated (and unavailable in iOS7) "[UIDevice currentDevice] setOrientation". While it does work, I have a feeling this is something that would get rejected by Apple, else I would mark this as a correct answer.Naima
When I try this it ONLY works on iPad. Both iPad Mini (iOS 7.1) and iPhone 5s (iOS 7.1) don't work. The simulator for all devices worksSeaware
@Seaware Make sure You have set all the mode for orientation for your application,Doubletime
@JohnStewart there is no issue with this method. i have used in multiple application everytime it is accepted by appleDoubletime
It doesn't work on iPad Air either. Strange. I had this exact solution and was testing last night on an iPad 3, today at work the code just doesn't rotate.Regina
@hariszaman I am not sure,, But i have used in many projects apple didnt rejected..Doubletime
@hariszaman my app also got approved by apple. So there will not be any prob with this.But this does not work for iPhone 5s (iOS 7.1).Testimony
Is there any way I can get this to work without the animation?Koweit
This is gold, especially when you use UIViewController.attemptRotationToDeviceOrientation() and #24928557 you can get any view setup you want!Etui
Thank you very much @SidShah ! Should be the accepted answer !Mme
@C4-Travis - if it doesn't work on certain devices, probably the device has ORIENTATION LOCK on in user settings!Premillennial
Just got two crashes during testing, using this method. Does anyone know of another way?Magnifico
As some others have mentioned. When setting the device orientation manually, the delegate method for supported orientations is not always called, so this solution works intermittently. If you follow this code up with a call to UIViewController.attemptRotationToDeviceOrientation(), then it is always called for me. This may be a new requirement since this was answered, you may want to revise to add this detail.Maible
B
21

Use this. Perfect solution to orientation problem..ios7 and earlier

[[UIDevice currentDevice] setValue:
    [NSNumber numberWithInteger: UIInterfaceOrientationPortrait]
        forKey:@"orientation"];
Bust answered 27/8, 2014 at 12:36 Comment(8)
Works even on iOS 8. Perfect.Nightstick
This is accepted by the Apple app review team (for now) but I wouldn't say that it is "accepted by Apple". That is to say, it is a hack to use key-value coding and might stop working in any future iOS release.Overslaugh
@Arpan Where should add this code, i have added in viewwillapear but not working, please explain.Mangonel
@arpan_techisavy: I have tried with all, didload, willapear, didapear. One of my project is stuck in ios8 due to Orientation issue. i want all view in portrait but one view(video player) with landscape mode, i have checked portrait and left landscape mode in storyboard. I hv tried all code but not working, i think i am missing something. Please advice and if you have Demo please hare.Mangonel
@Josef Rysanek:I have tried with all, didload, willapear, didapear. One of my project is stuck in IOS-8 due to Orientation issue. i want all view in portrait but one view(video player) with landscape mode, i have checked portrait and left landscape mode in storyboard. I hv tried all code but not working, i think i am missing something. Please advice and if you have Demo please hare.Mangonel
@arpan_techisavy How will I know if this method already finished executing?Discant
Compressed version: [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft) forKey:@"orientation"];Janey
how do you reset the orientation to all values, both landscape and portrait ?Masteratarms
B
10

You need to call attemptRotationToDeviceOrientation (UIViewController) to make the system call your supportedInterfaceOrientations when the condition has changed.

Bubal answered 8/1, 2014 at 5:3 Comment(3)
In my code, I tried replacing the application setStatusBarOrientation method with [UIViewController attemptRotationToDeviceOrientation], as you suggest. However, it seems to have no effect, alas.Naima
I find this thread also where, while your suggestions seems to be the "right answer", it just doesn't work: #19125763Naima
this only works if device orientation is different than UIViewController's orientationHindrance
D
7
NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft]; [[UIDevice currentDevice] setValue:value forKey:@"orientation"];

does work but you have to return shouldAutorotate with YES in your view controller

- (BOOL)shouldAutorotate
{
    return self.shouldAutoRotate;
}

But if you do that, your VC will autorotate if the user rotates the device... so I changed it to:

@property (nonatomic, assign) BOOL shouldAutoRotate;

- (BOOL)shouldAutorotate
{
    return self.shouldAutoRotate;
}

and I call

- (void)swithInterfaceOrientation:(UIInterfaceOrientation)orientation
{
    self.rootVC.shouldAutoRotate = YES;

    NSNumber *value = [NSNumber numberWithInt: orientation];
    [[UIDevice currentDevice] setValue:value forKey:@"orientation"];
}

to force a new orientation with a button-click. To set back shouldAutoRotate to NO, I added to my rootVC

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    self.shouldAutoRotate = NO;
}

PS: This workaround does work in all simulators too.

Duckboard answered 6/4, 2015 at 13:2 Comment(2)
Funny.. I was searching for something like that and what did I found? The only solution for iOS 11 and I wrote it myself 1,5 years ago.. And I realized it when I wanted to give a +1 for the solution :DDuckboard
wow... sir you deserve an upvote, this is by far the cleanest solution i've seenCollodion
Y
4

This works for me on Xcode 6 & 5.

- (BOOL)shouldAutorotate {
    return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
    return (UIInterfaceOrientationMaskPortrait);
}
Yellowtail answered 12/10, 2014 at 13:37 Comment(0)
L
3

The only way that worked for me is presenting dummy modal view controller.

UIViewController* dummyVC = [[UIViewController alloc] init];
dummyVC.view = [[UIView alloc] init];
[self presentModalViewController:dummyVC animated:NO];
[self dismissModalViewControllerAnimated:NO];

Your VC will be asked for updated interface orientations when modal view controller is dismissed.

Curious thing is that UINavigationController does exactly this when pushing/popping child view controllers with different supported interface orientations (tested on iOS 6.1, 7.0).

Lenardlenci answered 2/4, 2014 at 9:41 Comment(1)
As mentioned elsewhere, this no longer works on iOS 8.Dillon
W
3

If you have a UIViewController that should stay in Portrait mode, simply add this override and you're all set.

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.Portrait
}

The best part is there is no animation when this view is shown, it's just already in the correct orientation.

Wilhite answered 4/8, 2016 at 17:49 Comment(0)
V
3

This solution lets you force a certain interface orientation, by temporarily overriding the value of UIDevice.current.orientation and then asking the system to rotate the interface to match the device's rotation:

Important: This is a hack, and could stop working at any moment

Add the following in your app's root view controller:

class RootViewController : UIViewController {
    private var _interfaceOrientation: UIInterfaceOrientation = .portrait
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return UIInterfaceOrientationMask(from: _interfaceOrientation) }
    override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { return _interfaceOrientation }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Register for notifications
        NotificationCenter.default.addObserver(self, selector: #selector(RootViewController.handleInterfaceOrientationChangeRequestedNotification(_:)), name: .interfaceOrientationChangeRequested, object: nil)
    }

    deinit { NotificationCenter.default.removeObserver(self) }

    func handleInterfaceOrientationChangeRequestedNotification(_ notification: Notification) {
        guard let interfaceOrientation = notification.object as? UIInterfaceOrientation else { return }
        _interfaceOrientation = interfaceOrientation
        // Set device orientation
        // Important:
        // • Passing a UIDeviceOrientation here doesn't work, but passing a UIInterfaceOrientation does
        // • This is a hack, and could stop working at any moment
        UIDevice.current.setValue(interfaceOrientation.rawValue, forKey: "orientation")
        // Rotate the interface to the device orientation we just set
        UIViewController.attemptRotationToDeviceOrientation()
    }
}

private extension UIInterfaceOrientationMask {

    init(from interfaceOrientation: UIInterfaceOrientation) {
        switch interfaceOrientation {
        case .portrait: self = .portrait
        case .landscapeLeft: self = .landscapeLeft
        case .landscapeRight: self = .landscapeRight
        case .portraitUpsideDown: self = .portraitUpsideDown
        case .unknown: self = .portrait
        }
    }
}

extension Notification.Name {
    static let interfaceOrientationChangeRequested = Notification.Name(rawValue: "interfaceOrientationChangeRequested")
}

Make sure all interface orientations are checked under "Deployment Info":

Interface Orientations

Request interface orientation changes where you need them:

NotificationCenter.default.post(name: .interfaceOrientationChangeRequested, object: UIInterfaceOrientation.landscapeRight)
Veto answered 22/3, 2017 at 0:26 Comment(0)
P
2

If you want to lock the main view of your app to portrait, but want to open popup views in landscape, and you are using tabBarController as rootViewController as I am, you can use this code on your AppDelegate.

AppDelegate.h

@interface AppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UITabBarController *tabBarController;

@end

AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    // Create a tab bar and set it as root view for the application
    self.tabBarController = [[UITabBarController alloc] init];
    self.tabBarController.delegate = self;
    self.window.rootViewController = self.tabBarController;

    ...
}

- (NSUInteger)tabBarControllerSupportedInterfaceOrientations:(UITabBarController *)tabBarController
{
    return UIInterfaceOrientationMaskPortrait;
}

- (UIInterfaceOrientation)tabBarControllerPreferredInterfaceOrientationForPresentation:(UITabBarController *)tabBarController
{
    return UIInterfaceOrientationPortrait;
}

It works very well.

In your viewController you want to be presented in landscape, you simply use the following:

- (NSUInteger)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskLandscape;
}

- (BOOL)shouldAutorotate {
    return YES;
}
Pallmall answered 7/3, 2014 at 14:37 Comment(2)
Interesting. I'll play around with this when I have my app restructured. Currently there is no container view controller, but I've been intending to wrap it in one anyway. Thanks.Naima
For me the last block of code worked fine! But I also don't have any UINavigationController or UITabBarController integrated so maybe its a little difference otherwise.Drone
E
2
  1. Add this statement into AppDelegate.h

    //whether to allow cross screen marker 
    @property (nonatomic, assign) allowRotation BOOL;
    
  2. Write down this section of code into AppDelegate.m

    - (UIInterfaceOrientationMask) application: (UIApplication *) supportedInterfaceOrientationsForWindow: application (UIWindow *) window {
        If (self.allowRotation) {
            UIInterfaceOrientationMaskAll return;
        }
        UIInterfaceOrientationMaskPortrait return;
    }
    
  3. Change the allowRotation property of delegate app

Elissa answered 29/6, 2016 at 14:51 Comment(1)
Welcome to StackOverflow. Could you please format the code a bit better to make it more readable?Possibly
B
2

For those like me who struggled to get @Sunny Shah accepted answer to work on iPads. You need to set the "Requires full screen" checkbox on in the project settings. Note that this will prevent your app from working on multitasking mode which may or not be acceptable.

enter image description here

Bowl answered 10/4, 2019 at 3:29 Comment(0)
N
1

The base UINavigationController should have the below callback so that the child items can decide what orientation they want.

-(NSUInteger)supportedInterfaceOrientations {
    UIViewController *topVC = self.topViewController;
    return topVC.supportedInterfaceOrientations;
}

-(BOOL)shouldAutorotate {
   UIViewController *topVC = self.topViewController;
   return [topVC shouldAutorotate];
}
Nutriment answered 28/4, 2015 at 9:2 Comment(0)
N
1

If you want only portrait mode, in iOS 9 (Xcode 7) you can:

  • Going to Info.plist
  • Select "Supported interface orientations" item
  • Delete "Landscape(left home button)" and "Landscape(right home button)"

enter image description here

Naseberry answered 29/2, 2016 at 15:9 Comment(0)
M
1

I was in a similar problem than you. I need to lock device orientation for some screens (like Login) and allow rotation in others.

After a few changes and following some answers below I did it by:

  • Enabling all the orientations in the Project's Info.plist.

enter image description here

  • Disabling orientation in those ViewControllers where I need the device not to rotate, like in the Login screen in my case. I needed to override shouldAutorotate method in this VC:

-(BOOL)shouldAutorotate{ return NO; }

Hope this will work for you.

Milanmilanese answered 3/5, 2016 at 19:45 Comment(0)
C
1

here it is a FULL WORKING example for iOS 7, 8, 9, 10 how to change app orientation to its current opposite

Objective-C

- (void)flipOrientation
{
    NSNumber *value;
    UIInterfaceOrientation currentOrientation = [[UIApplication sharedApplication] statusBarOrientation];
    if(UIInterfaceOrientationIsPortrait(currentOrientation))
    {
        if(currentOrientation == UIInterfaceOrientationPortrait)
        {
            value = [NSNumber numberWithInt:UIInterfaceOrientationPortraitUpsideDown];
        }
        else //if(currentOrientation == UIInterfaceOrientationPortraitUpsideDown)
        {
            value = [NSNumber numberWithInt:UIInterfaceOrientationPortrait];
        }
    }
    else
    {
        if(currentOrientation == UIInterfaceOrientationLandscapeRight)
        {
            value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];
        }
        else //if(currentOrientation == UIInterfaceOrientationLandscapeLeft)
        {
            value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight];
        }
    }
    [[UIDevice currentDevice] setValue:value forKey:@"orientation"];
    [UIViewController attemptRotationToDeviceOrientation];
}

Swift 3

func flipOrientation() -> Void
{
    let currentOrientation : UIInterfaceOrientation = UIApplication.shared.statusBarOrientation
    var value : Int = 0;
    if(UIInterfaceOrientationIsPortrait(currentOrientation))
    {
        if(currentOrientation == UIInterfaceOrientation.portrait)
        {
            value = UIInterfaceOrientation.portraitUpsideDown.rawValue
        }
        else //if(currentOrientation == UIInterfaceOrientation.portraitUpsideDown)
        {
            value = UIInterfaceOrientation.portrait.rawValue
        }
    }
    else
    {
        if(currentOrientation == UIInterfaceOrientation.landscapeRight)
        {
            value = UIInterfaceOrientation.landscapeLeft.rawValue
        }
        else //if(currentOrientation == UIInterfaceOrientation.landscapeLeft)
        {
            value = UIInterfaceOrientation.landscapeRight.rawValue
        }
    }
    UIDevice.current.setValue(value, forKey: "orientation")
    UIViewController.attemptRotationToDeviceOrientation()
}
Carlita answered 19/7, 2017 at 13:24 Comment(3)
this solution uses reflection technique, as iOS does not have / display any public method to accomplish this objective, so we have to do it vie reflectionCarlita
Well, ultimately we are modifying the things Apple does not want us to: UIDevice's orientation property is ready-only. Have you faced rejection of app for this code ever?Homburg
sorry, but I can't answer, as the app where I user this piece of code is released outside iTunes, did you try to submit some code like this?Carlita
H
0

Try this along with your code.

-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation  

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration

once user select any option then call this method because user can be in landscape mode and then he can set only portrait mode in same view controller so automatically view should be moved to portrait mode so in that button acton call this

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
Hax answered 8/1, 2014 at 4:53 Comment(7)
I'm not quite sure what you're suggesting, but shouldAutorotateToInterfaceOrientation looks to be deprecated in iOS7 in favor of shouldAutorotate, which I have implemented.Naima
sorry, my mistake. use shouldAutorotate only and -(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)durationHax
I added [self willRotateToInterfaceOrientation:UIInterfaceOrientationPortrait duration:ROTATE_VIEW_DELAY]; to the method where the heads-up mode is selected, replacing Sunny Shah's objc_msgSend answer above. Alas, still no effect on the display. It's my understanding that "willrotate" is called when the display is about to rotate, in response to the user physically moving device.Naima
@John Stewart we can call this method independently... But tell me what is the code you wrote in this method. If possible can you update your question with all code you have written for orientation (all methods you used)Hax
Charan Giri - I have created a simple test project to demonstrate: github.com/johnstewart/RotateTest. The project is a simple iPad app with an ImageView, and a switch for turning "portrait-only" on and off. I started with a blank view-only project, and added this property: @property (nonatomic) bool modePortraitOnly;Naima
- (NSUInteger) supportedInterfaceOrientations { if (self.modePortraitOnly) { return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; } else { return UIInterfaceOrientationMaskAll; } } and when control changes, I simply do ` [self willRotateToInterfaceOrientation:UIInterfaceOrientationPortrait duration:1.0]; ` but as you can see if you try it, it does nothing. Comment out this line and add Sunny shah's above and it does work.Naima
To test, run app. Uncheck box. Rotate to landscape. Check box. You'll see that the orientation does not change.Naima
H
0

This worked me perfectly....

NSNumber *value = [NSNumber numberWithInt:UIDeviceOrientationPortrait];
[[UIDevice currentDevice] setValue:value forKey:@"orientation"];
Halfhardy answered 1/2, 2018 at 12:10 Comment(0)
U
0

2020 Swift 5 :

override var supportedInterfaceOrientations:UIInterfaceOrientationMask {
    return .portrait
}
Upheave answered 25/8, 2020 at 9:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.