74
votes

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?

18

18 Answers

197
votes

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

21
votes

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

[[UIDevice currentDevice] setValue:
    [NSNumber numberWithInteger: UIInterfaceOrientationPortrait]
        forKey:@"orientation"];
10
votes

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

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

4
votes

This works for me on Xcode 6 & 5.

- (BOOL)shouldAutorotate {
    return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
    return (UIInterfaceOrientationMaskPortrait);
}
3
votes

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

3
votes

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.

3
votes

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)
2
votes

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;
}
2
votes
  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

2
votes

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

1
votes

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];
}
1
votes

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

1
votes

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.

1
votes

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()
}
1
votes

2020 Swift 5 :

override var supportedInterfaceOrientations:UIInterfaceOrientationMask {
    return .portrait
}
0
votes

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
0
votes

This worked me perfectly....

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