89

I am trying to force only one view in my application on landscape mode, I am calling:

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

override func supportedInterfaceOrientations() -> Int {
    print("supportedInterfaceOrientations")
    return Int(UIInterfaceOrientationMask.LandscapeLeft.rawValue)
}

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

The view is launched in the portrait mode, and keep rotating when I change the device orientation. The shouldAutorotate() method is never called.

Any help would be appreciated.

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
sazz
  • 3,193
  • 3
  • 23
  • 33
  • https://stackoverflow.com/a/46025791/2012219 – hbk Sep 29 '17 at 21:23
  • Possible duplicate of [How to force view controller orientation in iOS 8?](https://stackoverflow.com/questions/26357162/how-to-force-view-controller-orientation-in-ios-8) – hbk Sep 29 '17 at 21:23

21 Answers21

89

It may be useful for others, I found a way to force the view to launch in landscape mode:

Put this in the viewDidLoad():

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

and,

override var shouldAutorotate: Bool {
    return true
}
Tung Fam
  • 7,899
  • 4
  • 56
  • 63
sazz
  • 3,193
  • 3
  • 23
  • 33
  • 6
    I’ve just tried using this and it didn’t work; although changing shouldAutorotate() to return false worked. Just to be clear: I wanted it to stay in Portrait no matter what. (Also changed UIInterfaceOrientation.LandscapeLeft to UIInterfaceOrientation.Portrait of course) – milo526 Mar 12 '15 at 21:49
  • 2
    it's because your view load in Portrait mode and you want it to stay like that, my view load in Portrait mode and I want it to show in Landscape mode. If I put shouldAutorotate to NO, it will not show in Landscape – sazz Mar 13 '15 at 08:39
  • Thanks!! This worked great for me on an iOS9 app with Swift 2.2 to force portrait. I started with a 'parent' view in landscape, put the above code in viewDidLoad of the ViewController which i present modally from the 'parent', and when the modal view was presented, the view rotated to portrait. When i dismissed the modal view, the 'parent' remained in portrait (though i bet you could store a 'previous orientation' value before showing the modal and set the 'parent' view back to the previous orientation, if you wanted to swanky about it! – Natalia May 19 '16 at 01:32
  • 5
    Could this cause Apple to reject your app? orientation is a readonly property, and you're manipulating the value. I would think Apple would frown upon this. – pulse4life Sep 26 '16 at 14:00
  • If the device was initially oriented LandscapeRight, would you really want everything to suddenly appear upside down? – Edward Brey Sep 12 '17 at 09:58
  • 1
    worked for me on iOS 14, Swift 5.3, only by overriding shouldAutorotate. – quarac Sep 29 '20 at 19:03
68

Swift 4

override func viewDidLoad() {
    super.viewDidLoad()
    let value = UIInterfaceOrientation.landscapeLeft.rawValue
    UIDevice.current.setValue(value, forKey: "orientation")
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .landscapeLeft
}

override var shouldAutorotate: Bool {
    return true
}

If your view is embedded in a navigation controller, the above alone won't work. You have to cascade up by the following extension after the class definition.

extension UINavigationController {

override open var shouldAutorotate: Bool {
    get {
        if let visibleVC = visibleViewController {
            return visibleVC.shouldAutorotate
        }
        return super.shouldAutorotate
    }
}

override open var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{
    get {
        if let visibleVC = visibleViewController {
            return visibleVC.preferredInterfaceOrientationForPresentation
        }
        return super.preferredInterfaceOrientationForPresentation
    }
}

override open var supportedInterfaceOrientations: UIInterfaceOrientationMask{
    get {
        if let visibleVC = visibleViewController {
            return visibleVC.supportedInterfaceOrientations
        }
        return super.supportedInterfaceOrientations
    }
}}


Swift 3

override func viewDidLoad() {
    super.viewDidLoad()
    let value = UIInterfaceOrientation.landscapeLeft.rawValue
    UIDevice.current.setValue(value, forKey: "orientation")
}

private func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.landscapeLeft
}

private func shouldAutorotate() -> Bool {
    return true
}
Peter
  • 29
  • 1
  • 5
Manee ios
  • 1,112
  • 11
  • 14
  • 5
    This code doesn't work on iOS 12. App gets crash and shows the error like "Supported orientations has no common orientation with the application and [PlayerViewController shouldAutorotate] is returning YES" – Mayur Rathod Oct 19 '18 at 09:14
  • @MayurRathod did you find the fix to ur crash. – Shirish Jan 22 '19 at 09:49
  • @Shirish Yes I implemented orientation change code in delegate file – Mayur Rathod Jan 22 '19 at 10:21
  • 1
    @MayurRathod If you have time, could you please post your answer – Mark Apr 04 '19 at 01:38
  • The Solution works fine for the iPhone but in iPad the presenting controller and hence the entire App is oriented to landscape. Any idea where can I be wrong? – user550088 Apr 08 '20 at 23:51
38

Swift 4 , Tested in iOS 11

You can specify the orientation in projectTarget -> General -> DeploymentInfo(Device Orientation) -> Portrait (Landscapeleft and Landscaperight are optional)

AppDelegate

    var myOrientation: UIInterfaceOrientationMask = .portrait
    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        return myOrientation
    }

LandScpaeViewController

override func viewDidLoad() {
        super.viewDidLoad()
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        appDelegate.myOrientation = .landscape
}

OnDismissButtonTap

let appDelegate = UIApplication.shared.delegate as! AppDelegate
 appDelegate.myOrientation = .portrait

Thats it. :)

Zeesha
  • 991
  • 9
  • 14
  • 3
    supportedInterfaceOrientationsFor is getting called only if device is rotated once. – Naga Mallesh Maddali Dec 19 '17 at 12:06
  • This is what I've been looking for, thanks! For anyone wondering the difference, this will present the new view in the rotation without animation. Where as setting the orientation on UIDevice will add a default rotation animation. – PostCodeism Aug 23 '20 at 21:20
  • omg yess!! this works pretty well, thank you! – JJ S. Aug 13 '21 at 15:50
22

Using Swift 2.2

Try:

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

Followed By:

UIViewController.attemptRotationToDeviceOrientation()

From Apple's UIViewController Class Reference:

Some view controllers may want to use app-specific conditions to determine what interface orientations are supported. If your view controller does this, when those conditions change, your app should call this class method. The system immediately attempts to rotate to the new orientation.

Then, as others have suggested, override the following methods as appropriate:

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

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

I was having a similar issue with a signature view and this solved it for me.

Aidan Malone
  • 400
  • 3
  • 7
  • 1
    Thanks for this answer man, This comment > `UIViewController.attemptRotationToDeviceOrientation()` Is very important because sometimes controller does not know if the rotation is to be performed or not. But this tells it to perform the rotation accordingly. – Ramandeep Singh Gosal Jun 05 '18 at 06:20
21

In AppDelegate add this:

//Orientation Variables
var myOrientation: UIInterfaceOrientationMask = .portrait

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

Add this in viewController, that want to change orientation:

override func viewDidLoad() {
        super.viewDidLoad()
        self.rotateToLandsScapeDevice()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.rotateToPotraitScapeDevice()
    }

    func rotateToLandsScapeDevice(){
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        appDelegate.myOrientation = .landscapeLeft
        UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
        UIView.setAnimationsEnabled(true)
    }

    func rotateToPotraitScapeDevice(){
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        appDelegate.myOrientation = .portrait
        UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
        UIView.setAnimationsEnabled(true)
    }
Luckabo
  • 74
  • 5
Elangovan
  • 1,158
  • 1
  • 9
  • 26
18

For me, the best results came from combining Zeesha's answer and sazz's answer.

Add the following lines to AppDelegate.swift:

var orientationLock = UIInterfaceOrientationMask.portrait
var myOrientation: UIInterfaceOrientationMask = .portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return myOrientation
}  

Add the following line to your view controller class:

let appDel = UIApplication.shared.delegate as! AppDelegate

Add the following lines to your view controller's viewDidLoad():

appDel.myOrientation = .landscape
UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")

(optional) Add this line to your dismiss function:

appDel.myOrientation = .portrait
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")

What these lines of code do is set the default orientation to portrait, rotate it landscape when the view controller loads, and then finally reset it back to portrait once the view controller closes.

jscs
  • 63,694
  • 13
  • 151
  • 195
Microbob
  • 672
  • 5
  • 20
  • 3
    This works for me too, because in my Project -> Deployment info I just have Portrait mode selected. Thanks – Dasoga Aug 08 '18 at 03:39
  • 1
    I tried many methods over the years. This is by far the easiest solution, no iterating over all stacked controllers etc. Hope it will last :) – Oritm Aug 24 '20 at 20:55
12

Overwrite (in ViewController):

override public var shouldAutorotate: Bool {
    return false
} 

override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .landscapeRight
}

override public var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
    return .landscapeRight
}

Hint for ios 13. As of ios 13, VC has different modalPresentationStyle as .automatic and device present modal view instead of Full-Screen VC. To fix this one must set modalPresentationStyle to .fullScreen. Example:

let viewController = YourViewController()
viewController.modalPresentationStyle = .fullScreen
  • 4
    viewController.modalPresentationStyle = .fullScreen helped me. Thanks! – anoo_radha Jul 31 '20 at 19:03
  • This only worked for me when the viewController is presented using `.present(viewController, animated: false, completion: nil)`.. NOT when pushing viewController using `.pushViewController(viewController, animated: true)` – YodagamaHeshan Nov 08 '21 at 02:24
  • This is partially work. BUT my case I've one navigation controller where I've presented landscape screen and when user switch to other app I need to present appLockScreen on another navigation controller (which works fine). Once user done with appLockScreen new navigation controller is dismissed and old landscaped screen is auto potraitted. :( – Chetan Prajapati Aug 22 '22 at 14:32
9

I needed to force one controller into portrait orientation. Adding this worked for me.

swift 4 with iOS 11

override var   supportedInterfaceOrientations : UIInterfaceOrientationMask{

    return  .portrait

}
Rob Schlackman
  • 309
  • 3
  • 6
3

I faced a similar issue in my project. It only has support for portrait. The ViewController structure is that, Navigation contained a controller (I called A), and a long Scrollview in A controller. I need A(portrait) present to B(landscape right).

In the beginning I tried the method below and it seemed to work but eventually I found a bug in it.

Swift 5 & iOS12

// In B controller just override three properties

override var shouldAutorotate: Bool {
    return false
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.landscapeRight
}

override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
    return .landscapeRight
}

And then something become strange. When controller B dismiss to controller A. The ScrollView in controller A has been slid some point.

So I used another method, so I rotate the screen when viewWillAppear. You can see the code for that below.

// In controller B
// not need override shouldAutorotate , supportedInterfaceOrientations , preferredInterfaceOrientationForPresentation

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    let appDel = UIApplication.shared.delegate as! AppDelegate
    appDel.currentOrientation = .landscapeRight
    UIDevice.current.setValue( UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
    UIViewController.attemptRotationToDeviceOrientation()
}

//in viewWillDisappear rotate to portrait can not fix the bug


override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
    let appDel = UIApplication.shared.delegate as! AppDelegate
    appDel.currentOrientation = .portrait
    UIDevice.current.setValue( UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
    UIViewController.attemptRotationToDeviceOrientation() //must call 
    super.dismiss(animated: true, completion: nil)
}
// in AppDelegate
// the info plist is only supported portrait also, No need to change it

var currentOrientation : UIInterfaceOrientationMask = .portrait


func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return self.currentOrientation
}
geisterfurz007
  • 5,292
  • 5
  • 33
  • 54
kkklc
  • 149
  • 7
2

Works in Swift 2.2

 func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
    if self.window?.rootViewController?.presentedViewController is SignatureViewController {

        let secondController = self.window!.rootViewController!.presentedViewController as! SignatureViewController

        if secondController.isPresented {

            return UIInterfaceOrientationMask.LandscapeLeft;

        } else {

            return UIInterfaceOrientationMask.Portrait;
        }

    } else {

        return UIInterfaceOrientationMask.Portrait;
    }
}
idris yıldız
  • 2,097
  • 20
  • 22
  • Does the code above go in AppDelegate or just in the ViewController that you're trying to force to portrait? – Natalia May 19 '16 at 01:21
2

Swift 3. This locks the orientation each time the user re-opens the app.

class MyViewController: UIViewController {
    ...
    override func viewDidLoad() {
        super.viewDidLoad()

        // Receive notification when app is brought to foreground
        NotificationCenter.default.addObserver(self, selector: #selector(self.onDidBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
    }

    // Handle notification
    func onDidBecomeActive() {
        setOrientationLandscape()
    }

    // Change orientation to landscape
    private func setOrientationLandscape() {
        if !UIDevice.current.orientation.isLandscape {
            let value = UIInterfaceOrientation.landscapeLeft.rawValue
            UIDevice.current.setValue(value, forKey:"orientation")
            UIViewController.attemptRotationToDeviceOrientation()
        }
    }

    // Only allow landscape left
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return UIInterfaceOrientationMask.landscapeLeft
    }

    /*
    // Allow rotation - this seems unnecessary
    private func shouldAutoRotate() -> Bool {
        return true
    }
    */
    ...
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Greg T
  • 3,278
  • 3
  • 37
  • 39
2

Swift 4

Trying to keep the orientation nothing worked but this for me:

...        
override func viewDidLoad() {
       super.viewDidLoad()
       forcelandscapeRight()
       let notificationCenter = NotificationCenter.default
       notificationCenter.addObserver(self, selector: #selector(forcelandscapeRight), name: Notification.Name.UIDeviceOrientationDidChange, object: nil)
    }

    @objc func forcelandscapeRight() {
        let value = UIInterfaceOrientation.landscapeRight.rawValue
        UIDevice.current.setValue(value, forKey: "orientation")
    }
....
Daxito
  • 71
  • 8
2

In ViewController in viewDidLoad Method call below function

func rotateDevice(){
    UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
    UIView.setAnimationsEnabled(true) // while rotating device it will perform the rotation animation
}`

App Delegate File Add Below Function & Variables

//Orientation Variables
var orientationLock = UIInterfaceOrientationMask.portrait
var myOrientation: UIInterfaceOrientationMask = .portrait

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { return .landscape }
Mayur Rathod
  • 361
  • 3
  • 13
1

According to the documentation of supportedInterfaceOrientations the shouldAutorotate method should return true or YES in Objective-C so that the supportedInterfaceOrientations are considered.

Sebastian Wramba
  • 10,087
  • 8
  • 41
  • 58
1

iOS 16+: requestGeometryUpdate(_:errorHandler:) API

As noted by @simonbs on Twitter, iOS 16 introduces a new API to update the current interface orientation. While in most cases, other, conventional methods will do the job, in some edge cases, they don't work (forcing the use of private APIs like suggested in this answer). Here, the new public API comes to the rescue.

The API works as follows:

windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait))

You can optionally also pass a closure to handle errors (though I have no experience under which circumstances errors may occur):

windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) { error in
   // Handle error...
}
fredpi
  • 8,414
  • 5
  • 41
  • 61
  • 2
    iOS 16 Error encountered: ```Error Domain=UISceneErrorDomain Code=101 "None of the requested orientations are supported by the view controller. Requested: portrait; Supported: landscapeLeft, landscapeRight" UserInfo={NSLocalizedDescription=None of the requested orientations are supported by the view controller. Requested: portrait; Supported: landscapeLeft, landscapeRight}``` – iHTCboy Sep 07 '22 at 04:14
  • 1
    @fredpi thanks you for sharing. Will you please share how can I achieve this for iPhone single screen in landscape for UIWindow usage? – Chetan Prajapati Sep 15 '22 at 07:19
1

Tested on IOS 13 to 16.4

First, create a protocol for your rotatable view controller:

protocol Rotatable:UIViewController{}

Then inside your add below code in your AppDelegate:

 func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    guard let topVC = UIApplication.getTopViewController(base: window?.rootViewController) as? Rotatable else {
        return .portrait
    }
    guard !topVC.isModal else {
        //Use isBeingDismissed to prevent rotation glitches when changing the top view controller.
        return topVC.isBeingDismissed ? .portrait : topVC.supportedInterfaceOrientations
    }

    return topVC.supportedInterfaceOrientations
}

You also need the below extensions:

extension UIApplication {

    static func getTopViewController(base: UIViewController? = rootViewController) -> UIViewController? {
        
        if let nav = base as? UINavigationController {
            return getTopViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            if let selected = tab.selectedViewController {
                return getTopViewController(base: selected)
            }
        }

        if let presented = base?.presentedViewController {
            return getTopViewController(base: presented)
        }

        return base
    }
    
    static var rootViewController:UIViewController?{
      return UIApplication
            .shared
            .connectedScenes
            .flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
            .last { $0.isKeyWindow }?.rootViewController
    }

}


extension UIViewController{
    
    var isModal: Bool {

        let presentingIsModal = presentingViewController != nil
        let presentingIsNavigation = navigationController?.presentingViewController?.presentedViewController == navigationController
        let presentingIsTabBar = tabBarController?.presentingViewController is UITabBarController

        return presentingIsModal || presentingIsNavigation || presentingIsTabBar
    }
    
}

After that you just need to override the below variables in your view controllers:

class MyViewController: UIViewController,Rotatable {

     override var shouldAutorotate: Bool {
            return true
        }
        
     override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
            return .landscape
        }

}
0

My solution is

just added below codes in AppDelegate

enum PossibleOrientations {
  case portrait
    case landscape

    func o() -> UIInterfaceOrientationMask {
      switch self {
      case .portrait:
        return .portrait
      case .landscape:
        return .landscapeRight
      }
    }
}
var orientation: UIInterfaceOrientationMask = .portrait

func switchOrientation(to: PossibleOrientations) {
    let keyOrientation = "orientation"

    if to == .portrait && UIDevice.current.orientation.isPortrait {
        return
    } else if to == .landscape && UIDevice.current.orientation.isLandscape {
        return
    }

    switch to {
    case .portrait:
        orientation = .portrait
        UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: keyOrientation)
    case .landscape:
        orientation = .landscapeRight
        UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: keyOrientation)
    }
}

And call below codes to change

override func viewDidLoad() {
    super.viewDidLoad()

    if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
        appDelegate.switchOrientation(to: .landscape)
    }
}

or like below

@IBAction func actBack() {
    if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
        appDelegate.switchOrientation(to: .portrait)
    }
    self.navigationController?.popViewController(animated: true)
}
Utku
  • 145
  • 1
  • 13
0
// below code put in view controller
// you can change landscapeLeft or portrait

override func viewWillAppear(_ animated: Bool) {
        UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
    }

override var shouldAutorotate: Bool {
        return true
    }
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return .landscapeRight
    }
    override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
        return .landscapeRight
    }
twodayslate
  • 2,803
  • 3
  • 27
  • 43
Muthuraj M
  • 99
  • 1
  • 3
0

I tried many of the answers below but I'm afraid they didn't work. Especially if nav bars and tab bars are also implemented on the app. The solution that worked for me was provided by Sunny Lee on this post here: Sunny Lee, Medium post

Which in turn is an update of this post: Original solution

The only change I made when implementing the post's solution, was to change the part which references .allButUpsideDown to .landscapeleft

Zenman C
  • 1,653
  • 13
  • 21
0

In Xcode 11 with Swift 5 I Implemented the following. But it only works when the device orientation for the project target does not include all orientations. I disabled the check for Upside Down. After this, the following code works. If all checkboxes are enabled, the code is not called;

class MyController : UINavigationController {

    override var shouldAutorotate: Bool {
        return true
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return .landscape
    }


}
Raymond
  • 3,630
  • 2
  • 19
  • 22
-2
class CustomUIViewController : UIViewController{

    override var   supportedInterfaceOrientations : UIInterfaceOrientationMask{

        return  .landscapeLeft

    }

}


class ViewController: CustomUIViewController {
.
.
.
}
Tal
  • 17
  • Welcome to stack overflow, how to write answer properly check [here](https://stackoverflow.com/help/how-to-answer). – Rahul Sharma May 20 '17 at 18:23