How to lock orientation of one view controller to portrait mode only?
Asked Answered
P

18

146

Since my app got support for all orientation. I would like to lock only portrait mode to specific UIViewController.

e.g. assume it was Tabbed Application and when SignIn View appear modally, I only want that SignIn View to the portrait mode only no matter how the user rotate the device or how the current device orientation will be

Pomfret answered 9/3, 2015 at 9:29 Comment(2)
This may helps https://mcmap.net/q/160851/-only-one-view-landscape-mode/6521116Adp
please refer to this answerCretaceous
I
374

Things can get quite messy when you have a complicated view hierarchy, like having multiple navigation controllers and/or tab view controllers.

This implementation puts it on the individual view controllers to set when they would like to lock orientations, instead of relying on the App Delegate to find them by iterating through subviews.

Swift 3, 4, 5

In AppDelegate:

/// set orientations you want to be allowed in this property by default
var orientationLock = UIInterfaceOrientationMask.all

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

In some other global struct or helper class, here I created AppUtility:

struct AppUtility {

    static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
    
        if let delegate = UIApplication.shared.delegate as? AppDelegate {
            delegate.orientationLock = orientation
        }
    }

    /// OPTIONAL Added method to adjust lock and rotate to the desired orientation
    static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {
   
        self.lockOrientation(orientation)
    
        UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
        UINavigationController.attemptRotationToDeviceOrientation()
    }

}

Then in the desired ViewController you want to lock orientations:

 override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    AppUtility.lockOrientation(.portrait)
    // Or to rotate and lock
    // AppUtility.lockOrientation(.portrait, andRotateTo: .portrait)
    
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    // Don't forget to reset when view is being removed
    AppUtility.lockOrientation(.all)
}

If iPad or Universal App

Make sure that "Requires full screen" is checked in Target Settings -> General -> Deployment Info. supportedInterfaceOrientationsFor delegate will not get called if that is not checked. enter image description here

Interruption answered 23/1, 2017 at 17:8 Comment(27)
Looks like it's code control...let me check it,seems like good answer.Pomfret
After a lot of insanely research, this is the only answer I found that works for me. Swift 3 - Xcode 8.2.1Ailment
Another great solution for Swift 3. I will updated this and refer to you.Pomfret
@ThihaAung, when I uncomment the AppUtility.lockOrientation(.portrait, andRotateTo: .landscape) . I got error. UIInterfaceorientation has no member 'landscape.' The reason why I change landscape is above code does not effect in my app and I would like to be is when user click on movie play view controller, then goes to another page and immediately rotates to Landscape. I would like to do this. Could you help me bro?Citation
@bmjohns, when I uncomment the AppUtility.lockOrientation(.portrait, andRotateTo: .landscape) . I got error. UIInterfaceorientation has no member 'landscape.' The reason why I change landscape is above code does not effect in my app and I would like to be is when user click on movie play view controller, then goes to another page and immediately rotates to Landscape. I would like to do this. Could you help me bro?Citation
@MayPhyu Check out the properties of UIInterfaceOrientation, you need to use landscapeLeft or .landscapeRight. developer.apple.com/reference/uikit/uiinterfaceorientationInterruption
@MayPhyu, your requirement and mine are the same. Please post a question to get clear more detail for us. Mine was setting the all the app UI to portrait. But, when I move to video view controller, I want it enable portrait,landscape left and right. So, user can see the video as they want to.Pomfret
@Interruption Hi, I am trying to use de Swift 3.0 answer but it doesn't work. If I have my phone on landscape, when I reach the ViewController where I want to force Portrait it doesn't change itLesser
@CarlosLuisUrbina Make sure that you use AppUtility.lockOrientation(.portrait, andRotateTo: .portrait) in viewDidAppear. There are two functions in my answer, one is to just lock orientations, and the other is to rotate and lock. If you always want the device to rotate if not in desired position then always use lockOrientation(andRotateTo:) functionInterruption
This solution works for me. But I'm concerned Apple will not approve an App using this code. Has anyone used this solution and had their App rejected or approved by Apple?Taddeusz
@Taddeusz Several of the applications at the company I work at lock orientations at specific parts throughout the app, and have all been approved to the app store multiple times, over many years. That said, though, I can't guarantee Apple will not have a problem with this down the road. But so far no issues, and I am fairly certain many apps do this in some form or another.Interruption
@NileshPol I have it working in an iPad and iPhone application. You might want to make sure you have all the orientations supported in the plist, also are the methods being called like you expect when debugging?Interruption
This is the only solution that worked for me. +1 Swift 3 - Xcode up to dateAlvira
If checking Requires full screen, that will prevent the app available to Slide Over and Split View. See Adopting Multitasking Enhancements on iPad by Apple. I have an answer doesn't require enable Requires full screenInchoation
@JohnPang Yes it does prevent it. But if you uncheck Requires full screen then all orientation delegates will not fire in an iPad application (iPhone they still will). That includes shouldAutorotate, preferredInterfaceOrientation, and supportedInterfaceOrientations. Your answer still uses supportedInterfaceOrientationsFor. Did you only test in iPhone?Interruption
@Interruption I didn't notice that application(_:supportedInterfaceOrientationsFor) didn't get fired on iPad until you mentioned. My app runs in portrait on iPhone, except an image viewer. Due to Smart Cover, I believe many users will be using iPad in landscape. I found the way to get it fired on iPad without Requires full screen. In the Info.plist, reduce both Supported interface orientations and Supported interface orientations (iPad) down to just one item: Portrait (bottom home button). However, the app won't appears in Slide Over anymore. Slide Over is just portrait, isn't it?Inchoation
I found an issue where if you select all the orientations in the Deployment Info and start the app while the app is in landscape, it will come out as landscape regardless of locking the screen to portrait. A fix is to just select portrait in the deployment info.Professor
Its not working, second viewController layout not updated its showing in portrait only.Shipment
Ameya Vichare. Thats not a solution.Bonnell
Works seamlessly. Thank you so much! Had to change a bit of code here and there to smoothly handle the transition from one ViewContoller to another when in different orientations. Except for that. WOW!Oxonian
This works well for both iPhone and iPad, but in some cases you should move the code from viewWillAppear to viewDidAppear (especially for SpriteKit enviroments..) to permit the correct layout drawing => rotate and use AppUtility.lockOrientation(.portrait, andRotateTo: .portrait) Richelieu
Anyone with a Swift 5 and iOS 13 version? This one isn't working anymore on iOS 13 for me :(Stratus
neat and well explained solution!Lavation
Working on latest iOS 14.6 :) Thanks!Michealmicheil
Ensure interface orientation is not specified in your Info.plist, otherwise the delegate method will not be called.Indulgent
Does not work with iOS 16 anymore. Throws following error: BUG IN CLIENT OF UIKIT: Setting UIDevice.orientation is not supported. Please use UIWindowScene.requestGeometryUpdate(_:).Jordonjorey
attemptRotationToDeviceOrientation is deprecated :(Doubles
S
46

Swift 4/5

Project -> General enter image description here AppDelegate

var orientationLock = UIInterfaceOrientationMask.all
    
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return self.orientationLock
}
struct AppUtility {
    static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
        if let delegate = UIApplication.shared.delegate as? AppDelegate {
            delegate.orientationLock = orientation
        }
    }
        
    static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {
        self.lockOrientation(orientation)
        UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
    }
}

Your ViewController Add the following line if you need only portrait orientation. you have to apply this to all ViewController need to display portrait mode.

override func viewWillAppear(_ animated: Bool) {
    AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.portrait, andRotateTo: UIInterfaceOrientation.portrait)
}

and that will make screen orientation for others Viewcontroller according to device physical orientation.

override func viewWillDisappear(_ animated: Bool) {
    AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.all)
}
Sip answered 9/4, 2018 at 9:25 Comment(3)
For Swift 4 this is definitely the top answer. It is exactly what I needed as it mimics the behavior of the iOS camera app.Shakespeare
@Nahid Do we replace the original func application within app delegate with this function func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { return self.orientationLock ? Saving
After hour or two of looking for solution and some attempts, this is the perfectly working solution. It set the orientation of view, forces it when view is loaded, unlock when going to another view, it works in tab navigation and the code is simple and clean. Thank you @NahidRemission
B
20

Swift 3 & 4

Set the supportedInterfaceOrientations property of specific UIViewControllers like this:

class MyViewController: UIViewController {

    var orientations = UIInterfaceOrientationMask.portrait //or what orientation you want
    override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
    get { return self.orientations }
    set { self.orientations = newValue }
    }

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

    //...
}

UPDATE

This solution only works when your viewController is not embedded in UINavigationController, because the orientation inherits from parent viewController.
For this case, you can create a subclass of UINavigationViewController and set these properties on it.

Beanstalk answered 7/3, 2019 at 7:49 Comment(2)
Thanks. This works well for me because I only want to restrict a specific View to Portrait. Not the entire app.Badillo
All I need is to control specific view controllers to lock orientation regardless of devise orientation and this worked great!Tropical
B
17

For a new version of Swift try this

override var shouldAutorotate: Bool {
    return false
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.portrait
}

override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
    return UIInterfaceOrientation.portrait
}
Booklover answered 1/3, 2020 at 10:37 Comment(1)
It is exactly what I was needed ! For iPad controller all orientation options are valid and for iPhone controller only portrait mode by writing in it only these three overrides - Perfect.Hailey
F
11

Add this code to force portrait and lock it:

override func viewDidLoad() {
    super.viewDidLoad()

    // Force the device in portrait mode when the view controller gets loaded
    UIDevice.currentDevice().setValue(UIInterfaceOrientation.Portrait.rawValue, forKey: "orientation") 
}

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

override func supportedInterfaceOrientations() -> Int {

    // Only allow Portrait
    return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}

override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {

    // Only allow Portrait
    return UIInterfaceOrientation.Portrait
}

In your AppDelegate - set supportedInterfaceOrientationsForWindow to whatever orientations you want the entire application to support:

func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.All
} 
Freestyle answered 9/3, 2015 at 11:22 Comment(1)
Thanks Valentin,I really appreciated your helps.But,you code didn't work out.And I have solved now.I also answered to my question.Pomfret
S
8

This is a generic solution for your problem and others related.

1. Create auxiliar class UIHelper and put on the following methods:

    /**This method returns top view controller in application  */
    class func topViewController() -> UIViewController?
    {
        let helper = UIHelper()
        return helper.topViewControllerWithRootViewController(rootViewController: UIApplication.shared.keyWindow?.rootViewController)
    }

    /**This is a recursive method to select the top View Controller in a app, either with TabBarController or not */
    private func topViewControllerWithRootViewController(rootViewController:UIViewController?) -> UIViewController?
    {
        if(rootViewController != nil)
        {
            // UITabBarController
            if let tabBarController = rootViewController as? UITabBarController,
                let selectedViewController = tabBarController.selectedViewController {
                return self.topViewControllerWithRootViewController(rootViewController: selectedViewController)
            }

            // UINavigationController
            if let navigationController = rootViewController as? UINavigationController ,let visibleViewController = navigationController.visibleViewController {
                return self.topViewControllerWithRootViewController(rootViewController: visibleViewController)
            }

            if ((rootViewController!.presentedViewController) != nil) {
                let presentedViewController = rootViewController!.presentedViewController;
                return self.topViewControllerWithRootViewController(rootViewController: presentedViewController!);
            }else
            {
                return rootViewController
            }
        }

        return nil
    }

2. Create a Protocol with your desire behavior, for your specific case will be portrait.

protocol orientationIsOnlyPortrait {}

Nota: If you want, add it in the top of UIHelper Class.

3. Extend your View Controller

In your case:

class Any_ViewController: UIViewController,orientationIsOnlyPortrait {

   ....

}

4. In app delegate class add this method:

 func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        let presentedViewController = UIHelper.topViewController()
        if presentedViewController is orientationIsOnlyPortrait {
            return .portrait
        }
        return .all
    }

Final Notes:

  • If you that more class are in portrait mode, just extend that protocol.
  • If you want others behaviors from view controllers, create other protocols and follow the same structure.
  • This example solves the problem with orientations changes after push view controllers
Sphery answered 27/3, 2017 at 19:21 Comment(1)
Like this solution, but I found the UIHelper functions more useful as extensions to UINavigationController.Beaumarchais
Q
6

A bunch of great answers in this thread, but none quite matched my needs. I have a tabbed app with navigation controllers in each tab, and one view needed to rotate, while the others needed to be locked in portrait. The navigation controller wasn't resizing it's subviews properly, for some reason. Found a solution (in Swift 3) by combining with this answer, and the layout issues disappeared. Create the struct as suggest by @bmjohns:

import UIKit

struct OrientationLock {

    static func lock(to orientation: UIInterfaceOrientationMask) {
        if let delegate = UIApplication.shared.delegate as? AppDelegate {
            delegate.orientationLock = orientation
        }
    }

    static func lock(to orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation: UIInterfaceOrientation) {
        self.lock(to: orientation)
        UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
    }
} 

Then subclass UITabBarController:

    import UIKit

class TabBarController: UITabBarController, UITabBarControllerDelegate {
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.delegate = self
    }

    func tabBarControllerSupportedInterfaceOrientations(_ tabBarController: UITabBarController) -> UIInterfaceOrientationMask {
        if tabBarController.selectedViewController is MyViewControllerNotInANavigationControllerThatShouldRotate {
            return .allButUpsideDown
        } else if let navController = tabBarController.selectedViewController as? UINavigationController, navController.topViewController is MyViewControllerInANavControllerThatShouldRotate {
            return .allButUpsideDown
        } else {
            //Lock view that should not be able to rotate
            return .portrait
        }
    }

    func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
        if viewController is MyViewControllerNotInANavigationControllerThatShouldRotate {
            OrientationLock.lock(to: .allButUpsideDown)
        } else if let navController = viewController as? UINavigationController, navController.topViewController is MyViewControllerInANavigationControllerThatShouldRotate {
            OrientationLock.lock(to: .allButUpsideDown)
        } else {
            //Lock orientation and rotate to desired orientation
            OrientationLock.lock(to: .portrait, andRotateTo: .portrait)
        }
        return true
    }
}

Don't forget to change the class of the TabBarController in the storyboard to the newly created subclass.

Quackery answered 1/3, 2017 at 17:14 Comment(0)
S
5

For iOS 16 the accepted answer wasn't working. but was able to get it working. Just replace

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

with this,

if #available(iOS 16.0, *) {
        guard
            let rootViewController = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.rootViewController,
            let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
        else { return }
        rootViewController.setNeedsUpdateOfSupportedInterfaceOrientations()
        windowScene.requestGeometryUpdate(.iOS(
            interfaceOrientations: windowScene.interfaceOrientation.isLandscape
                ? .portrait
                : .landscapeRight
        ))
    } else {
        UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
    }
Scenario answered 30/12, 2022 at 7:25 Comment(3)
thank you it worked for ios 16 in my case I had the same error in console like this link: developer.apple.com/forums/thread/712247Peluso
Perfect, works!!!Evolve
Is this the only modification you are doing? Nothing in AppDelegate? For some reason it is not working for me in iOS 16.5Interracial
O
4

Here is a simple way that works for me with Swift 4.2 (iOS 12.2), put this in a UIViewController for which you want to disable shouldAutorotate:

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .portrait
}

The .portrait part tells it in which orientation(s) to remain, you can change this as you like. Choices are: .portrait, .all, .allButUpsideDown, .landscape, .landscapeLeft, .landscapeRight, .portraitUpsideDown.

Orthoptic answered 17/4, 2019 at 4:35 Comment(0)
L
3

Best Solution for lock and change orientation on portrait and landscape:

Watch this video on YouTube:

https://m.youtube.com/watch?v=4vRrHdBowyo

This tutorial is best and simple.

or use below code:

See this picture

// 1- in second viewcontroller we set landscapeleft and in first viewcontroller we set portrat:

// 2- if you use NavigationController, you should add extension

import UIKit

class SecondViewController: UIViewController {


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

    override open var shouldAutorotate: Bool {
        return false
    }

    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return .landscapeLeft
    }

    override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
        return .landscapeLeft
    }
    
    

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

//write The rest of your code in here


}

//if you use NavigationController, you should add this extension

extension UINavigationController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return topViewController?.supportedInterfaceOrientations ?? .allButUpsideDown
    
    }
}
Leticialetisha answered 24/7, 2020 at 1:37 Comment(0)
O
2

To set Landscape orientation to all view of your app & allow only one view to All orientations (to be able to add camera roll for example):

In AppDelegate.swift:

var adaptOrientation = false

In: didFinishLaunchingWithOptions

NSNotificationCenter.defaultCenter().addObserver(self, selector: "adaptOrientationAction:", name:"adaptOrientationAction", object: nil)

Elsewhere in AppDelegate.swift:

func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int {
    return checkOrientation(self.window?.rootViewController)
}

func checkOrientation(viewController:UIViewController?)-> Int{
    if (adaptOrientation == false){
        return Int(UIInterfaceOrientationMask.Landscape.rawValue)
    }else {
        return Int(UIInterfaceOrientationMask.All.rawValue)
    }
}

func adaptOrientationAction(notification: NSNotification){
    if adaptOrientation == false {
        adaptOrientation = true
    }else {
        adaptOrientation = false
    }
}

Then in the view that segue to the one you want to be able to have All orientations:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
    if (segue.identifier == "YOURSEGUE") {
        NSNotificationCenter.defaultCenter().postNotificationName("adaptOrientationAction", object: nil)
    }
}

override func viewWillAppear(animated: Bool) {
    if adaptOrientation == true {
        NSNotificationCenter.defaultCenter().postNotificationName("adaptOrientationAction", object: nil)
    }
}

Last thing is to tick Device orientation: - Portrait - Landscape Left - Landscape Right

Orgulous answered 18/9, 2015 at 3:16 Comment(0)
K
2

Create new extension with

import UIKit

extension UINavigationController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return .portrait
    }
}

extension UITabBarController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return .portrait
    }
}
Kisung answered 29/5, 2018 at 21:20 Comment(0)
C
2

Actual tested Solution for this.In my example I need my whole app should be in portrait mode, but only one screen's orientation should be in landscape mode. Make a Portrait orientation for app , by check only portrait mode

Code in AppDelegate as above answers described.

var orientationLock = UIInterfaceOrientationMask.all

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask 
{
  return self.orientationLock
}
struct AppUtility {  

static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
    if let delegate = UIApplication.shared.delegate as? AppDelegate {
        delegate.orientationLock = orientation
    }
}
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {
self.lockOrientation(orientation)     
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}  
}

Then write down this code before your landscape orientation viewcontroller will be presented/push.

override func viewWillAppear(_ animated: Bool) {  
super.viewWillAppear(animated)
AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.portrait, andRotateTo: UIInterfaceOrientation.portrait)
}  

Then write down this code in actual viewcontroller(For landscape view)

override func viewWillAppear(_ animated: Bool) {  
super.viewWillAppear(animated)
AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.landscape, andRotateTo: UIInterfaceOrientation.landscape)
}  
Correctitude answered 22/10, 2018 at 6:40 Comment(0)
E
1

bmjohns -> You are my life saviour. That is the only working solution (With the AppUtility struct)

I've created this class:

class Helper{
    struct AppUtility {

        static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {

            if let delegate = UIApplication.shared.delegate as? AppDelegate {
                delegate.orientationLock = orientation
            }
        }

        /// OPTIONAL Added method to adjust lock and rotate to the desired orientation
        static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {

            self.lockOrientation(orientation)

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

    }
}

and followed your instructions, and everything works perfectly for Swift 3 -> xcode version 8.2.1

Ectropion answered 10/2, 2017 at 8:50 Comment(0)
I
1

As of iOS 10 and 11, iPad supports Slide Over and Split View. To enable an app in Slide Over and Split View, Requires full screen must be unchecked. That means the accepted answer cannot be used if the app wants to support Slide Over and Split View. See more from Apple's Adopting Multitasking Enhancements on iPad here.

I have a solution that allows (1) unchecking Requires full screen, (2) just one function to be implemented in appDelegate (especially if you don't want to / can't modify the target view controllers), and (3) avoid recursive calls. No need of helper class nor extensions.

appDelegate.swift (Swift 4)

func application(_ application: UIApplication,
                 supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    // Search for the visible view controller
    var vc = window?.rootViewController
    // Dig through tab bar and navigation, regardless their order 
    while (vc is UITabBarController) || (vc is UINavigationController) {
        if let c = vc as? UINavigationController {
            vc = c.topViewController
        } else if let c = vc as? UITabBarController {
            vc = c.selectedViewController
        }
    }
    // Look for model view controller
    while (vc?.presentedViewController) != nil {
        vc = vc!.presentedViewController
    }
    print("vc = " + (vc != nil ? String(describing: type(of: vc!)) : "nil"))
    // Final check if it's our target class.  Also make sure it isn't exiting.
    // Otherwise, system will mistakenly rotate the presentingViewController.
    if (vc is TargetViewController) && !(vc!.isBeingDismissed) {
        return [.portrait]
    }
    return [.all]
}

Edit

@bmjohns pointed out that this function is not called on iPad. I verified and yes it was not called. So, I did a bit more testing and found out some facts:

  1. I unchecked Requires full screen because I want to enable Slide Over and Slide View on iPad. That requires the app to support all 4 orientation for iPad, in Info.plist: Supported interface orientations (iPad).

My app works same way as Facebook: on iPhone, most of the time it is locked to portrait. When viewing image in full screen, allows users to rotate landscape for better view. On iPad, users can rotate to any orientation in any view controllers. So, the app looks nice when iPad is stand on Smart Cover (landscape left).

  1. For iPad to call application(_:supportedInterfaceOrientationsFor), in Info.plist, only keep portrait for iPad. The app will lose Slide Over + Split View ability. But you can lock or unlock the orientation for any view controller, in just one place and no need to modify ViewController class.

  2. Finally, this function get called on view controller's life cycle, when view is displayed/removed. If your app need to lock/unlock/change orientation in other time, it might not work

Inchoation answered 5/1, 2018 at 20:37 Comment(2)
Is it working for iPhone ? I want show one of my viewController in landscape only.Shipment
Yes. work for iPhone. I want one of my view controller in both portrait and landscape, and return to portrait after dismissing it.Inchoation
A
1

I experimented a little bit and I managed to find clean solution for this problem. The approach is based on the view tagging via view->tag

In the target ViewController just assign the tag to the root view like in the following code example:

class MyViewController: BaseViewController {

  // declare unique view tag identifier
  static let ViewTag = 2105981;

  override func viewDidLoad()
  {
    super.viewDidLoad();
    // assign the value to the current root view
    self.view.tag = MyViewController.ViewTag;
  }

And finally in the AppDelegate.swift check if the currently shown view is the one we tagged:

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask
{
    if (window?.viewWithTag(DesignerController.ViewTag)) != nil {
        return .portrait;
    }
    return .all;
}

This approach has been tested with my simulator and seems it works fine.

Note: the marked view will be also found if current MVC is overlapped with some child ViewController in navigation stack.

Ankylose answered 1/11, 2019 at 20:38 Comment(0)
K
0

This solution works for me

AppDelegate

class AppDelegate: UIResponder, UIApplicationDelegate {
    static var orientationLock = UIInterfaceOrientationMask.all
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }
    
    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        return AppDelegate.orientationLock
    }
}

If use SwiftUI

@main
struct testApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

View

VStack {
    Button("Lock to Portrait") {
        AppDelegate.orientationLock = UIInterfaceOrientationMask.portrait
        UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
        UIViewController.attemptRotationToDeviceOrientation()
    }
    
    Button("Unlock Orientation") {
        AppDelegate.orientationLock = UIInterfaceOrientationMask.all
        UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
        UIViewController.attemptRotationToDeviceOrientation()
    }
}

enter image description here

But: 'attemptRotationToDeviceOrientation()' was deprecated in iOS 16.0: Please use instance method setNeedsUpdateOfSupportedInterfaceOrientations.

Koski answered 16/4 at 21:8 Comment(0)
A
-4

Thanks to @bmjohn's answer above. Here is a working Xamarin / C# version of the that answer's code, to save others the time of transcription:

AppDelegate.cs

 public UIInterfaceOrientationMask OrientationLock = UIInterfaceOrientationMask.All;
 public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application, UIWindow forWindow)
 {
     return this.OrientationLock;
 }

Static OrientationUtility.cs class:

public static class OrientationUtility
{
    public static void LockOrientation(UIInterfaceOrientationMask orientation)
    {
        var appdelegate = (AppDelegate) UIApplication.SharedApplication.Delegate;
        if(appdelegate != null)
        {
            appdelegate.OrientationLock = orientation;
        }
    }

    public static void LockOrientation(UIInterfaceOrientationMask orientation, UIInterfaceOrientation RotateToOrientation)
    {
        LockOrientation(orientation);

        UIDevice.CurrentDevice.SetValueForKey(new NSNumber((int)RotateToOrientation), new NSString("orientation"));
    }
}

View Controller:

    public override void ViewDidAppear(bool animated)
    {
       base.ViewWillAppear(animated);
       OrientationUtility.LockOrientation(UIInterfaceOrientationMask.Portrait, UIInterfaceOrientation.Portrait);
    }

    public override void ViewWillDisappear(bool animated)
    {
        base.ViewWillDisappear(animated);
        OrientationUtility.LockOrientation(UIInterfaceOrientationMask.All);
    }
Amphitheater answered 12/7, 2017 at 18:44 Comment(1)
I don't understand how this answer is relevant? That person asked in swift and you have given in Xamarin.Lavation

© 2022 - 2024 — McMap. All rights reserved.