155

Before iOS 8, we used below code in conjunction with supportedInterfaceOrientations and shouldAutoRotate delegate methods to force app orientation to any particular orientation. I used below code snippet to programmatically rotate the app to desired orientation. Firstly, I am changing the status bar orientation. And then just presenting and immediately dismissing a modal view rotates the view to desired orientation.

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight animated:YES];
UIViewController *c = [[UIViewController alloc]init];
[self presentViewController:vc animated:NO completion:nil];
[self dismissViewControllerAnimated:NO completion:nil];

But this is failing in iOS 8. Also, I have seen some answers in stack overflow where people suggested that we should always avoid this approach from iOS 8 onwards.

To be more specific, my application is a universal type of application. There are three controllers in total.

  1. First View controller- It should support all orientations in iPad and only portrait (home button down) in iPhone.

  2. Second View controller- It should support only landscape right in all conditions

  3. Third View controller- It should support only landscape right in all conditions

We are using navigation controller for page navigation. From the first view controller, on a button click action, we are pushing the second one on stack. So, when the second view controller arrives, irrespective of device orientation, the app should lock in landscape right only.

Below is my shouldAutorotate and supportedInterfaceOrientations methods in second and third view controller.

-(NSUInteger)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskLandscapeRight;
}

-(BOOL)shouldAutorotate {
    return NO;
}

Is there any solution for this or any better way of locking a view controller in particular orientation for iOS 8. Please help!!

Sunny Shah
  • 12,990
  • 9
  • 50
  • 86
Rashmi Ranjan mallick
  • 6,390
  • 8
  • 42
  • 59
  • Implementing the methods you mentioned in the *presented* VC should generally work (at least that's my experience in iOS 8). Perhaps your specific setup is causing issues? – Vladimir Gritsenko Oct 14 '14 at 09:29
  • May be I can make the question little bit clearer. I will edit the question slightly. – Rashmi Ranjan mallick Oct 14 '14 at 09:36
  • @VladimirGritsenko: Please check now. I have edited it. – Rashmi Ranjan mallick Oct 14 '14 at 09:48
  • The code in the question doesn't use navigation controller's stack, but rather the modal presentation, so it's still not clear what you're doing exactly. I will say that in our code, returning YES from shouldAutoRotate and returning the desired orientations from supportedInterfaceOrientations in the *presented* VC properly orients that VC. – Vladimir Gritsenko Oct 14 '14 at 09:52
  • Well, it's not really a failing, it's just a big change in concept. Whether it's good change is entirely another topic. – Kegluneq Oct 14 '14 at 10:37
  • @VladimirGritsenko: I am navigation controller only to push a view controller onto stack. Above code snippet was only to deliberately rotate the app to desired orientation. I added some more points in the questions. Please re-check. Sorry, if I not clear enough. – Rashmi Ranjan mallick Oct 14 '14 at 12:18
  • @user1963877: Thanks. Could you please let us know what is the change in iOS 8. I have no clue of that. It would be very helpful if you could post all the informations as an answer here. – Rashmi Ranjan mallick Oct 14 '14 at 12:20
  • So you mean that we should try to adopt to the iOS 8 Size Class? – Rashmi Ranjan mallick Oct 14 '14 at 12:59
  • Yes. Exactly. We need to iOS 6, 7 & 8 in our app. As you said it is going to be complex. As of now, I don't have a basic idea how to proceed. One doubt. How do I use this size classes to detect orientation. Could you give me a heads up. – Rashmi Ranjan mallick Oct 14 '14 at 13:16
  • Ohh Ok. I am sorry. Actually I am very new to iOS 8. Need to learn a lot. – Rashmi Ranjan mallick Oct 14 '14 at 13:49
  • But I have one big doubt. If I go for the Size class of iOS 8, how do I support for the older iOS versions? Any idea on that as well? – Rashmi Ranjan mallick Oct 14 '14 at 13:51
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/63033/discussion-between-rashmi-ranjan-mallick-and-user1963877). – Rashmi Ranjan mallick Oct 14 '14 at 13:56
  • I have got this working but when going from a landscape view controller to a portrait view controller, the view controller which should show in portrait is briefly shown in landscape before rotating on its own. if anyone knows how to help my question is here: http://stackoverflow.com/questions/30693964/swift-going-from-a-landscape-viewcontroller-to-a-portrait-viewcontroller – dwinnbrown Jun 11 '15 at 10:37
  • You guys can try to use my solution at [here](https://stackoverflow.com/questions/21216594/disable-rotation-for-view-controller-in-navigation-controller/46357119#46357119) . Hope this helps – Heo Đất Hades Sep 22 '17 at 04:58

25 Answers25

324

For iOS 7 - 10:

Objective-C:

[[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft) forKey:@"orientation"];
[UINavigationController attemptRotationToDeviceOrientation];

Swift 3:

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

Just call it in - viewDidAppear: of the presented view controller.

Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
Sunny Shah
  • 12,990
  • 9
  • 50
  • 86
  • 1
    I will try and let you know regarding this solution. But is it working for you? And what is the purpose you are using this for? Could you please clarify – Rashmi Ranjan mallick Oct 14 '14 at 12:23
  • I have used in MPMoviePlayer where i have to show the video in landscape forcefully – Sunny Shah Oct 15 '14 at 04:18
  • Could you please post some more code snippets regarding ur orientation handling – Rashmi Ranjan mallick Oct 15 '14 at 04:20
  • 1
    Hi Sid. It works really!!! There is one point here. When I include this code snippet, the view controller rotates to the desired orientation with the default rotation animation. Is there anyway I can stop that? Because I am using this code snippet immediately after navigation push animation. So, it looks little weird. Any help on this? – Rashmi Ranjan mallick Nov 10 '14 at 10:49
  • sorry can you elaborate this" the view controller rotates to the desired orientation with the default rotation animation" – Sunny Shah Nov 10 '14 at 11:22
  • Above code does rotate the interface orientation. But it rotates with an animation. I am not saying that the animation is bad! But can I make the rotation without animation? – Rashmi Ranjan mallick Nov 10 '14 at 14:37
  • Also, another doubt. Is this a private API. Will this make my app to be rejected by Apple? This is very very important. Please confirm. – Rashmi Ranjan mallick Nov 10 '14 at 14:37
  • Actually all the developers saying that this is private API. But i have used in all the version of my app. it succefully upload on the app store. – Sunny Shah Nov 10 '14 at 16:32
  • 1
    Hi. I'm not too sure if I'm doing it wrong, but if the device is already in Landscape Left when this line of code is processed, the interface doesn't rotate. Any pointers? – iamdavidlam Nov 12 '14 at 03:03
  • 63
    This is *not* private API. This is *unsecure* use of APIs. You are not using any private methods, but using 'internal things'. If Apple changes the value for "orientation", it fails. Worse: if Apple changes the meaning of the "orientation" value, you don't know what you are changing hard-setting the value. – sonxurxo Dec 20 '14 at 17:55
  • Unfortunately, this rotates the device too after the view has already appeared, so the effect isn't desirable. Is there a way to ensure the view controller opens already in the correct orientation? – jowie Jan 23 '15 at 16:08
  • Will the same work for my rootviewcontroller. My rootviewcontroller is a UITabBarController. Please suggest. – Abdul Yasin Feb 20 '15 at 05:02
  • Hi, Its perfectly working on iPhones but i am facing problem on my iPad. Please suggest me. – Abdul Yasin Apr 08 '15 at 13:44
  • whats the problem make sure you have selected platform for iPad to rotate – Sunny Shah Apr 09 '15 at 12:35
  • Thanks, but how about iPhone 6 Plus - when You open application, then minimize it (press home button), turn device on landscape, and then open application again. In this case it does not work (still iOS alerts, and possible to pull from top notification center (from landscape top) . (can be tested on simulator). – Guntis Treulands Apr 30 '15 at 15:22
  • How to do it without animation ? – Deepak Sharma Jul 11 '15 at 08:50
  • @jowie did you find a solution for opening the view controller in the correct orientation? – Crashalot Jul 14 '15 at 17:56
  • @RashmiRanjanmallick did you figure out how to lock orientation for a view controller without the user seeing any animation? – Crashalot Jul 16 '15 at 06:33
  • @Crashalot: Sorry, I haven't found any proper solution for this issue. – Rashmi Ranjan mallick Jul 20 '15 at 08:33
  • 4
    To disable animation, put `[UIView setAnimationsEnabled:NO];` in `viewWillAppear` and `[UIView setAnimationsEnabled:YES];` in `viewDidAppear`, related: http://stackoverflow.com/a/6292506/88597 – ohho Jul 28 '15 at 02:41
  • 1
    if you put this piece of code to viewWillAppear it will work as intented. – mkeremkeskin Aug 11 '15 at 11:58
  • 1
    Came here to say what kerem said above... viewWillAppear is a better place for this brilliant hack (many thanks to Sid for finding it!) because then the animations feel right when forcing a transition between landscape & portrait VCs. – Reuben Scratton Aug 28 '15 at 15:09
  • 3
    Nice hack, but the problem with that, is that is not firing the didRotateFromInterfaceOrientation after setting the new orientation value (sigh) – loretoparisi Sep 30 '15 at 14:50
  • 2
    In iOS 9.2, this is only changing orientation but not locking. – Mark13426 Dec 20 '15 at 04:35
  • In iOS 10.0.2 also only changing orientation but not locking anyway. – Preco Plusb Sep 28 '16 at 02:44
  • 1
    Orientation locking depends on `shouldAutorotate`, not `UIDevice.currentDevice().setValue` – khcpietro Sep 29 '16 at 17:02
  • 2
    For everyone having this behave good 90% of the time and buggy the other 10% of the time, call this after setting the orientation key: `[UINavigationController attemptRotationToDeviceOrientation];` – Albert Renshaw Mar 14 '18 at 03:42
  • But how to rotate back to current orientation when user press back in navigation bar ? I have one solution but it doesn't work all the time but how you all people worked this out ? – Renetik Jun 13 '19 at 12:12
96

Orientation rotation is a little more complicated if you are inside a UINavigationController or UITabBarController. The problem is that if a view controller is embedded in one of these controllers the navigation or tab bar controller takes precedence and makes the decisions on autorotation and supported orientations.

I use the following 2 extensions on UINavigationController and UITabBarController so that view controllers that are embedded in one of these controllers get to make the decisions.

Give View Controllers the Power!

Swift 2.3

extension UINavigationController {
    public override func supportedInterfaceOrientations() -> Int {
        return visibleViewController.supportedInterfaceOrientations()
    }
    public override func shouldAutorotate() -> Bool {
        return visibleViewController.shouldAutorotate()
    }
}

extension UITabBarController {
    public override func supportedInterfaceOrientations() -> Int {
        if let selected = selectedViewController {
            return selected.supportedInterfaceOrientations()
        }
        return super.supportedInterfaceOrientations()
    }
    public override func shouldAutorotate() -> Bool {
        if let selected = selectedViewController {
            return selected.shouldAutorotate()
        }
        return super.shouldAutorotate()
    }
}

Swift 3

extension UINavigationController {
    open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return visibleViewController?.supportedInterfaceOrientations ?? super.supportedInterfaceOrientations
    }

    open override var shouldAutorotate: Bool {
        return visibleViewController?.shouldAutorotate ?? super.shouldAutorotate
    }
}

extension UITabBarController {
    open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        if let selected = selectedViewController {
            return selected.supportedInterfaceOrientations
        }
        return super.supportedInterfaceOrientations
    }

    open override var shouldAutorotate: Bool {
        if let selected = selectedViewController {
            return selected.shouldAutorotate
        }
        return super.shouldAutorotate
    }
}

Now you can override the supportedInterfaceOrientations method or you can override shouldAutoRotate in the view controller you want to lock down otherwise you can leave out the overrides in other view controllers that you want to inherit the default orientation behavior specified in your app's plist

Disable Rotation

class ViewController: UIViewController {
    override func shouldAutorotate() -> Bool {
        return false
    }
}

Lock to Specific Orientation

class ViewController: UIViewController {
    override func supportedInterfaceOrientations() -> Int {
        return Int(UIInterfaceOrientationMask.Landscape.rawValue)
    }
}

In theory this should work for all complex view controller hierarchies, but I have noticed an issue with UITabBarController. For some reason it wants to use a default orientation value. See the following blog post if you are interested in learning about how to work around some of the issues:

Lock Screen Rotation

Kof
  • 23,893
  • 9
  • 56
  • 81
Korey Hinton
  • 2,532
  • 2
  • 25
  • 24
  • 2
    I know this is a very old post but where would you put the extension code? – dwinnbrown Jun 06 '15 at 17:46
  • 1
    And what would it look like in Objective-C? – fatuhoku Jun 09 '15 at 20:53
  • 1
    I now know how to add the extension. You create an empty swift file and call it something like: UINavigationController+(whatever you want) then you call it in the view controller where you want it to run. – dwinnbrown Jun 11 '15 at 10:36
  • @Korey, although visibleViewController for UINavigationController is an explicitly unwrapped optional, I got nil when presenting a UIImagePickerController. It's worth conditionally unwrapping that as well. Thanks, this worked for me. – rob5408 Jul 14 '15 at 03:16
  • 1
    This is not possible on Objective-C because class extensions can only be done if the source code is available. – ftvs Oct 22 '15 at 10:05
  • 20
    This is an nice solution for Swift and ObjC. Don't understand why people down voting. You got the idea and then write code is easy. Why complain? – Zhao Nov 19 '15 at 19:12
  • 1
    This causes problems when using a `UIAlertController`, so you may have to write an extension for that as well. – Huby Dec 01 '15 at 10:30
  • 1
    Not compiling in Swift 2.1 :-/ – Krodak Feb 08 '16 at 07:06
  • @ftvs of course it's possible, just use a category...it's the same as an extension in Swift. – Renan Kosicki Apr 19 '16 at 14:37
  • 2
    @Krodak , In the extensions try changing " -> Int" to "-> UIInterfaceOrientationMask". Did the trick for me. – the_pantless_coder May 10 '16 at 18:21
  • 2
    The idea and the code is perfect, i love it, but there is an issue when using UINavigationController, and going back from a UIViewController in displayed in landscape to one that has to be displayed in portrait. Any ideas? – crisisGriega Oct 19 '16 at 11:27
  • This code smell not very good as it extends the methods from its protocol in its extension/category, how about other extensions? – Itachi Jun 02 '17 at 17:30
25

This is what worked for me:

https://developer.apple.com/library//ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/clm/UIViewController/attemptRotationToDeviceOrientation

Call it in your viewDidAppear: method.

- (void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    [UIViewController attemptRotationToDeviceOrientation];
}
Vincil Bishop
  • 1,594
  • 17
  • 21
23

I found that if it's a presented view controller, you can override preferredInterfaceOrientationForPresentation

Swift:

override func supportedInterfaceOrientations() -> Int {
  return Int(UIInterfaceOrientationMask.Landscape.rawValue)
}

override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
  return UIInterfaceOrientation.LandscapeLeft
}

override func shouldAutorotate() -> Bool {
  return false
}
Zipme
  • 486
  • 3
  • 8
  • 4
    It seems that the accepted answer simply triggers an orientation change to landscape. What you've shown here is how to *force* a view controller to *only* be in landscape, and not affect the presenting view controller (which is exactly what I need). Thanks. – Clifton Labrum May 04 '15 at 17:01
  • 1
    @CliftonLabrum did you do anything else to force *only* landscape? Using the same code, the view controller still is able to rotate to another orientation. – Crashalot Jul 14 '15 at 07:58
  • I later realized that--you are correct. I haven't found a fix yet. – Clifton Labrum Jul 14 '15 at 17:43
  • 1
    This worked for me. I was able to keep a single View Controller in Portrait mode by using this code (replacing Landscape with Portrait). +1 – Mark Barrasso Aug 04 '15 at 18:12
  • Yes! This worked for me too in Xcode 7.3 & Swift 2.2. But still need to define the func application for supportedInterfaceOrientationsForWindow in AppDelegate. – charles.cc.hsu Apr 10 '16 at 23:56
  • In iOS 13 I had to add `self.modalPresentationStyle = UIModalPresentationFullScreen;` – Frank Hintsch Dec 28 '19 at 17:10
16

This way work for me in Swift 2 iOS 8.x:

PS (this method dont require to override orientation functions like shouldautorotate on every viewController, just one method on AppDelegate)

Check the "requires full screen" in you project general info. enter image description here

So, on AppDelegate.swift make a variable:

var enableAllOrientation = false

So, put also this func:

func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
        if (enableAllOrientation == true){
            return UIInterfaceOrientationMask.All
        }
        return UIInterfaceOrientationMask.Portrait
}

So, in every class in your project you can set this var in viewWillAppear:

override func viewWillAppear(animated: Bool)
{
        super.viewWillAppear(animated)
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        appDelegate.enableAllOrientation = true
}

If you need to make a choices based on the device type you can do this:

override func viewWillAppear(animated: Bool)
    {
        super.viewWillAppear(animated)
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        switch UIDevice.currentDevice().userInterfaceIdiom {
        case .Phone:
        // It's an iPhone
           print(" - Only portrait mode to iPhone")
           appDelegate.enableAllOrientation = false
        case .Pad:
        // It's an iPad
           print(" - All orientation mode enabled on iPad")
           appDelegate.enableAllOrientation = true
        case .Unspecified:
        // Uh, oh! What could it be?
           appDelegate.enableAllOrientation = false
        }
    }
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
12

First of all - this is a bad idea, in general, something wrong going with your app architecture, but, sh..t happens, if so, you can try to make something like below:

final class OrientationController {

    static private (set) var allowedOrientation:UIInterfaceOrientationMask = [.all]

    // MARK: - Public

    class func lockOrientation(_ orientationIdiom: UIInterfaceOrientationMask) {
        OrientationController.allowedOrientation = [orientationIdiom]
    }

    class func forceLockOrientation(_ orientation: UIInterfaceOrientation) {
        var mask:UIInterfaceOrientationMask = []
        switch orientation {
            case .unknown:
                mask = [.all]
            case .portrait:
                mask = [.portrait]
            case .portraitUpsideDown:
                mask = [.portraitUpsideDown]
            case .landscapeLeft:
                mask = [.landscapeLeft]
            case .landscapeRight:
                mask = [.landscapeRight]
        }

        OrientationController.lockOrientation(mask)

        UIDevice.current.setValue(orientation.rawValue, forKey: "orientation")
    }
}

Than, in AppDelegate

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // do stuff
    OrientationController.lockOrientation(.portrait)
    return true
}

// MARK: - Orientation

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return OrientationController.allowedOrientation
}

And whenever you want to change orientation do as:

OrientationController.forceLockOrientation(.landscapeRight)

Note: Sometimes, device may not update from such call, so you may need to do as follow

OrientationController.forceLockOrientation(.portrait)
OrientationController.forceLockOrientation(.landscapeRight)

That's all

dahiya_boy
  • 9,298
  • 1
  • 30
  • 51
hbk
  • 10,908
  • 11
  • 91
  • 124
9

This is a feedback to comments in Sid Shah's answer, regarding how to disable animations using:

[UIView setAnimationsEnabled:enabled];

Code:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:NO];
    [UIView setAnimationsEnabled:NO];

    // Stackoverflow #26357162 to force orientation
    NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight];
    [[UIDevice currentDevice] setValue:value forKey:@"orientation"];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:NO];
    [UIView setAnimationsEnabled:YES];
}
Community
  • 1
  • 1
ohho
  • 50,879
  • 75
  • 256
  • 383
8

This should work from iOS 6 on upwards, but I've only tested it on iOS 8. Subclass UINavigationController and override the following methods:

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationLandscapeRight;
}

- (BOOL)shouldAutorotate {
    return NO;
}

Or ask the visible view controller

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return self.visibleViewController.preferredInterfaceOrientationForPresentation;
}

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

and implement the methods there.

Mojo66
  • 1,109
  • 12
  • 21
  • I just tested this on my app in Xcode 8.2.1 running iOS 10.0 and it works. My game menu is the first screen and that does not rotate; all other views rotate. Perfect! Gave it a one up. I only needed the 'preferredInterfaceOrientationForPresentation' function given by Mojo66. –  Jan 05 '17 at 18:35
6

If you are using navigationViewController you should create your own superclass for this and override:

- (BOOL)shouldAutorotate {
  id currentViewController = self.topViewController;

  if ([currentViewController isKindOfClass:[SecondViewController class]])
    return NO;

  return YES;
}

this will disable rotation in SecondViewController but if you push your SecondViewController when your device is on portrait orientation then your SecondViewController will appear in portrait mode.

Assume that you are using storyboard. You have to create manual segue (How to) and in your "onClick" method:

- (IBAction)onPlayButtonClicked:(UIBarButtonItem *)sender {
  NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];
  [[UIDevice currentDevice] setValue:value forKey:@"orientation"];
  [self performSegueWithIdentifier:@"PushPlayerViewController" sender:self];
}

This will force landscape orientation before your superclass disable autorotate feature.

Community
  • 1
  • 1
Lau
  • 1,804
  • 1
  • 23
  • 49
6

On Xcode 8 the methods are converted to properties, so the following works with Swift:

override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.portrait
}

override public var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
    return UIInterfaceOrientation.portrait
}

override public var shouldAutorotate: Bool {
    return true
}
Ali Momen Sani
  • 840
  • 2
  • 11
  • 26
4

I have tried many solutions, but the one that worked for is the following:

There is no need to edit the info.plist in ios 8 and 9.

- (BOOL) shouldAutorotate {
    return NO;
}   

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return (UIInterfaceOrientationPortrait | UIInterfaceOrientationPortraitUpsideDown);
}

Possible orientations from the Apple Documentation:

UIInterfaceOrientationUnknown

The orientation of the device cannot be determined.

UIInterfaceOrientationPortrait

The device is in portrait mode, with the device held upright and the home button on the bottom.

UIInterfaceOrientationPortraitUpsideDown

The device is in portrait mode but upside down, with the device held upright and the home button at the top.

UIInterfaceOrientationLandscapeLeft

The device is in landscape mode, with the device held upright and the home button on the left side.

UIInterfaceOrientationLandscapeRight

The device is in landscape mode, with the device held upright and the home button on the right side.

GabLeRoux
  • 16,715
  • 16
  • 63
  • 81
hoogw
  • 4,982
  • 1
  • 37
  • 33
4

[iOS9+] If anyone dragged all the way down here as none of above solutions worked, and if you present the view you want to change orientation by segue, you might wanna check this.

Check your segue's presentation type. Mine was 'over current context'. When I changed it to Fullscreen, it worked.

Thanks to @GabLeRoux, I found this solution.

  • This changes only works when combined with solutions above.
Goh
  • 61
  • 3
3

The combination of Sids and Koreys answers worked for me.

Extending the Navigation Controller:

extension UINavigationController {
    public override func shouldAutorotate() -> Bool {
        return visibleViewController.shouldAutorotate()
    }
}

Then disabling rotation on the single View

class ViewController: UIViewController {
    override func shouldAutorotate() -> Bool {
        return false
    }
}

And rotating to the appropriate orientation before the segue

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if (segue.identifier == "SomeSegue")
    {
        let value = UIInterfaceOrientation.Portrait.rawValue;
        UIDevice.currentDevice().setValue(value, forKey: "orientation")
    }
}
Misha
  • 693
  • 5
  • 14
  • Do you know if this might help my problem where the view controller that is displayed is in landscape for a second before it rotates to portrait like it should always be? question is here: http://stackoverflow.com/questions/30693964/swift-going-from-a-landscape-viewcontroller-to-a-portrait-viewcontroller – dwinnbrown Jun 11 '15 at 10:39
3

The top solution above:

let value = UIInterfaceOrientation.LandscapeLeft.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")

didnt'work for me when I called it in viewDidAppear of the presented view controller. However it did work when I called it in preparForSegue in the presenting view controller.

(Sorry, not enough reputation points to comment on that solution, so I had to add it like this)

guido
  • 2,792
  • 1
  • 21
  • 40
  • I have got this working but when going from a landscape view controller to a portrait view controller, the view controller which should show in portrait is briefly shown in landscape before rotating on its own. if anyone knows how to help my question is here: http://stackoverflow.com/questions/30693964/swift-going-from-a-landscape-viewcontroller-to-a-portrait-viewcontroller – dwinnbrown Jun 11 '15 at 10:38
  • @dwinnbrown at viewDidAppear the view controller is already visible. Maybe try from viewDidLoad instead? – Crashalot Jul 12 '15 at 19:13
  • @Crashalot I have now fixed this. Please see the answer I marked as correct. – dwinnbrown Jul 13 '15 at 10:13
  • @dwinnbrown the link to your question is dead. update? thanks! – Crashalot Jul 13 '15 at 15:53
  • @Crashalot the community deleted it but I have voted to undelete it. I'll let you know soon – dwinnbrown Jul 13 '15 at 15:55
  • @dwinnbrown thanks, can you post the answer here or share what you learned at least as an answer? thanks! – Crashalot Jul 13 '15 at 15:59
  • @dwinnbrown this doesn't seem to work without animation? your view controllers get locked into an orientation without any animation? – Crashalot Jul 16 '15 at 06:34
  • My view controllers are locked without any animation but that is the way I wanted it. Please could you clarify what you are trying to say? Also I believe you have to add some code to the app delegate file for it to work if using a navigation controller. – dwinnbrown Jul 16 '15 at 08:18
2

My requirements are

  1. lock all views in portrait mode
  2. use AVPlayerViewController to play video

When video is playing, if it's a landscape then allow the screen to rotate landscape right and landscape left. If it's a portrait then lock the view in portrait mode only.

First, define supportedInterfaceOrientationsForWindow in AppDelegate.swift

var portrait = true
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
    if portrait {
        return .Portrait
    } else {
        return .Landscape
    }
}

Second, in your main view controller, define following functions

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    print("\(#function)")
    return .Portrait
}

override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
    return .Portrait
}

override func shouldAutorotate() -> Bool {
    return false
}

Then, you need to subclass AVPlayerViewController

class MyPlayerViewController: AVPlayerViewController {

    var size: CGSize?

    var supportedOrientationMask: UIInterfaceOrientationMask?
    var preferredOrientation: UIInterfaceOrientation?

    override func viewDidLoad() {
        super.viewDidLoad()

        if let size = size {
            if size.width > size.height {
                self.supportedOrientationMask =[.LandscapeLeft,.LandscapeRight]
                self.preferredOrientation =.LandscapeRight
            } else {
                self.supportedOrientationMask =.Portrait
                self.preferredOrientation =.Portrait
            }
        }
    }

Override these three functions in MyPlayerViewController.swift

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    return self.supportedOrientationMask!
}

override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
    return self.preferredOrientation!
}

Because user might rotate device landscape left or landscape right, we need to set auto rotate to be true

override func shouldAutorotate() -> Bool {
    return true
}

Finally, create MyPlayerViewController instance in your view controller and set the property size value.

let playerViewController = MyPlayerViewController()

// Get the thumbnail  
let thumbnail = MyAlbumFileManager.sharedManager().getThumbnailFromMyVideoMedia(......)

let size = thumbnail?.size
playerViewController.size = size

Initiate your player with proper videoUrl, then assign your player to playerViewController. Happy coding!

GabLeRoux
  • 16,715
  • 16
  • 63
  • 81
charles.cc.hsu
  • 689
  • 11
  • 15
2
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [UIViewController attemptRotationToDeviceOrientation];
}
Roman Solodyashkin
  • 799
  • 12
  • 17
1

According to Korey Hinton's answer

Swift 2.2:

extension UINavigationController {
    public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
        return visibleViewController!.supportedInterfaceOrientations()
    }
    public override func shouldAutorotate() -> Bool {
        return visibleViewController!.shouldAutorotate()
    }
}


extension UITabBarController {
    public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
        if let selected = selectedViewController {
            return selected.supportedInterfaceOrientations()
        }
        return super.supportedInterfaceOrientations()
    }
    public override func shouldAutorotate() -> Bool {
        if let selected = selectedViewController {
            return selected.shouldAutorotate()
        }
        return super.shouldAutorotate()
    }
}

Disable Rotation

override func shouldAutorotate() -> Bool {
    return false
}

Lock to Specific Orientation

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.Portrait
}
GabLeRoux
  • 16,715
  • 16
  • 63
  • 81
phnmnn
  • 12,813
  • 11
  • 47
  • 64
1

There still seems to be some debate about how best to accomplish this task, so I thought I'd share my (working) approach. Add the following code in your UIViewController implementation:

- (void) viewWillAppear:(BOOL)animated
{
    [UIViewController attemptRotationToDeviceOrientation];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
    return (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}

-(BOOL)shouldAutorotate
{
    return NO;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskLandscapeLeft;
}

For this example, you will also need to set your allowed device orientations to 'Landscape Left' in your project settings (or directly in info.plist). Just change the specific orientation you want to force if you want something other than LandscapeLeft.

The key for me was the attemptRotationToDeviceOrientation call in viewWillAppear - without that the view would not properly rotate without physically rotating the device.

ViperMav
  • 71
  • 3
1

I tried a few solutions in here and the important thing to understand is that it's the root view controller that will determine if it will rotate or not.

I created the following objective-c project github.com/GabLeRoux/RotationLockInTabbedViewChild with a working example of a TabbedViewController where one child view is allowed rotating and the other child view is locked in portrait.

It's not perfect but it works and the same idea should work for other kind of root views such as NavigationViewController. :)

Child view locks parent orientation

GabLeRoux
  • 16,715
  • 16
  • 63
  • 81
0

I have the same problem and waste so many time for it. So now I have my solution. My app setting is just support portrait only.However, some screens into my app need have landscape only.I fix it by have a variable isShouldRotate at AppDelegate. And the function at AppDelegate:

func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int {
    if isShouldRotate == true {
        return Int(UIInterfaceOrientationMask.Landscape.rawValue)
    }
    return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}

And finally when a ViewControllerA need landscape state. Just do that: before push/present to ViewControllerA assign isShouldRotate to true. Don't forget when pop/dismiss that controller assign isShouldRotate to false at viewWillDisappear.

GabLeRoux
  • 16,715
  • 16
  • 63
  • 81
lee
  • 7,955
  • 8
  • 44
  • 60
0

According to solution showed by @sid-sha you have to put everything in the viewDidAppear: method, otherwise you will not get the didRotateFromInterfaceOrientation: fired, so something like:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
    if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
        interfaceOrientation == UIInterfaceOrientationLandscapeRight) {
        NSNumber *value = [NSNumber numberWithInt:interfaceOrientation];
        [[UIDevice currentDevice] setValue:value forKey:@"orientation"];
    }
}
loretoparisi
  • 15,724
  • 11
  • 102
  • 146
0

My solution

In AppDelegate:

func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
    if let topController = UIViewController.topMostViewController() {
        if topController is XXViewController {
            return [.Portrait, .LandscapeLeft]
        }
    }
    return [.Portrait]
}

XXViewController is the ViewController you want to support Landscape mode.

Then Sunny Shah's solution would work in your XXViewController on any iOS version:

let value = UIInterfaceOrientation.LandscapeLeft.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")

This is the utility function to find the top most ViewController.

extension UIViewController {

    /// Returns the current application's top most view controller.
    public class func topMostViewController() -> UIViewController? {
        let rootViewController = UIApplication.sharedApplication().windows.first?.rootViewController
        return self.topMostViewControllerOfViewController(rootViewController)
    }



    /// Returns the top most view controller from given view controller's stack.
    class func topMostViewControllerOfViewController(viewController: UIViewController?) -> UIViewController? {
        // UITabBarController
        if let tabBarController = viewController as? UITabBarController,
           let selectedViewController = tabBarController.selectedViewController {
            return self.topMostViewControllerOfViewController(selectedViewController)
        }

        // UINavigationController
        if let navigationController = viewController as? UINavigationController,
           let visibleViewController = navigationController.visibleViewController {
            return self.topMostViewControllerOfViewController(visibleViewController)
        }

        // presented view controller
        if let presentedViewController = viewController?.presentedViewController {
            return self.topMostViewControllerOfViewController(presentedViewController)
        }

        // child view controller
        for subview in viewController?.view?.subviews ?? [] {
            if let childViewController = subview.nextResponder() as? UIViewController {
                return self.topMostViewControllerOfViewController(childViewController)
            }
        }

        return viewController
    }

}
Community
  • 1
  • 1
duan
  • 8,515
  • 3
  • 48
  • 70
0

Use this to lock view controller orientation, tested on IOS 9:

// Lock orientation to landscape right

-(UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskLandscapeRight;
}

-(NSUInteger)navigationControllerSupportedInterfaceOrientations:(UINavigationController *)navigationController {
    return UIInterfaceOrientationMaskLandscapeRight;
}
0

For me, the top level VC needed to implement the orientation overrides. Using VC's down the stack will have no effect if the top VC is not implementing.

VC-main
    |
    -> VC 2
        |
        -> VC 3

Only VC-Main is listened to, essentially in my testing.

bduhbya
  • 410
  • 5
  • 6
0

It looks like even thou here is so much answers no one was sufficient for me. I wanted to force orientation and then on going back go back to device orientation but [UIViewController attemptRotationToDeviceOrientation]; just did'nt work. What also did complicated whole thing is that I added shouldAutorotate to false based on some answer and could not get desired effects to rotate back correctly in all scenarios.

So this is what I did:

Before pushing of controller in call in his init constructor this:

_userOrientation = UIDevice.currentDevice.orientation;
[UIDevice.currentDevice setValue:@(UIInterfaceOrientationPortrait) forKey:@"orientation"];
[self addNotificationCenterObserver:@selector(rotated:)
                               name:UIDeviceOrientationDidChangeNotification];

So I save last device orientation and register for orientation change event. Orientation change event is simple:

- (void)rotated:(NSNotification*)notification {
    _userOrientation = UIDevice.currentDevice.orientation;
}

And on view dissmising I just force back to any orientation I have as userOreintation:

- (void)onViewDismissing {
    super.onViewDismissing;
    [UIDevice.currentDevice setValue:@(_userOrientation) forKey:@"orientation"];
    [UIViewController attemptRotationToDeviceOrientation];
}

And this has to be there too:

- (BOOL)shouldAutorotate {
    return true;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskPortrait;
}

And also navigation controller has to delegate to shouldAutorotate and supportedInterfaceOrientations, but that most people already have I believe.

PS: Sorry I use some extensions and base classes but names are quite meaningful so concept is understandable, will make even more extensions because it's not too much pretty now.

Renetik
  • 5,887
  • 1
  • 47
  • 66