Swift 5, lock first VC to Potrait mode, other VC support all orientations

Viewed 454

I am trying to build an app in Swift 5 which has a locked VC in potrait mode. In the next VC i want the orientation to support all orientations. I have tried several different solutions and none of them seem to work. I don't have Navigation Controller or TabBar. My application is now locked in potrait on both VC:s. I do support different device orientations in the general settings tab.

This is my current code in AppDelegate:

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

First VC:

override var shouldAutorotate: Bool {
    return false
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.portrait
}

override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
    return UIInterfaceOrientation.portrait
}

Second VC:

override var shouldAutorotate: Bool {
    return false
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.all
}
6 Answers

This is unfortunately a tricky subject. But if you have no UINavigationController or UITabBarController it should be straightforward.

In the AppDelegate, like you've already done, put:

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

In the first UIViewController set:

// This stops the controller from rotating
override var shouldAutorotate: Bool {
  false
}

// This will rotate it back to portrait once it's presented again
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
  .portrait
}

In the second one you don't need anything because shouldAutorotate defaults to true.

NOTE: If you aren't using a UINavigationController you are probably presenting the second controller. The second controller has to have a modalPresentationStyle of either .fullScreen or .currentContext.

I tried it in a sample project and it's the only way I got it to work.

1, First of all, enable all orientation in Info.plist.

2, Create protocol anywhere

protocol RotatableViewController {
    // No content necessary, marker protocol
}

3, Add this to Appdelegate:

extension AppDelegate {

    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        guard let rootVc = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController), rootVc.isBeingDismissed == false, let _ = rootVc as? RotatableViewController else { return .portrait } //swiftlint:disable:this all
        return .allButUpsideDown
    }

    private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
        if rootViewController == nil {
            return nil
        }
        if rootViewController.isKind(of: UITabBarController.self) {
            guard let root = rootViewController as? UITabBarController else { return UITabBarController() }
            return topViewControllerWithRootViewController(rootViewController: root.selectedViewController)
        } else if rootViewController.isKind(of: UINavigationController.self) {
            guard let root = rootViewController as? UINavigationController else { return UINavigationController() }
            return topViewControllerWithRootViewController(rootViewController: root.visibleViewController)
        } else if rootViewController.presentedViewController != nil {
            return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
        }
        return rootViewController
    }
}

4, Add RotatableViewController to your controllers, where you want to enable rotation.

class ViewController: UIViewController, RotatableViewController

Have you added the UISupportedInterfaceOrientations declare in your Info.plist?

You may turn it on in target config panel: device orientation

Next, remove application(_:supportedInterfaceOrientationsFor:) method in your AppDelegate. Just declare them in your view controllers.

enter image description here enter image description here Step 1: Add below code in your project.

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

Step 2: Add below code in AppDelegate class.

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

Step 3: Add below code in First view controller for lock potrait orientation.

override func viewWillAppear(_ animated: Bool) {
        AppUtility.lockOrientation(.portrait, andRotateTo: .portrait)
}
    
override func viewWillDisappear(_ animated: Bool) {
        //When controller Disappear unlock all orientation for other controllers.
        AppUtility.lockOrientation(.all)
}

you can add some utility like this

   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")
    UINavigationController.attemptRotationToDeviceOrientation()
}

and call it like this on view_willappear and view_willdisappear respectively

Utility.lockOrientation(.portrait, andRotateTo: .portrait)

in AppDelegate return the value of orientationlock in below delegate method

 var orientationLock = UIInterfaceOrientationMask.all

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

For Swift 4, 5+

Just add this line on your ViewController:

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

For Portrait only:

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

For Landscape:

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

Or can set landscapeLeft, landscapeRight

Hope it will work for you.

Related