how to lock portrait orientation for only main view using swift
Asked Answered
G

16

47

I have created an application for iPhone, using swift, that is composed from many views embedded in a navigation controller. I would like to lock the main view to Portrait orientation and only a subview of a navigation controller locked in Landscape orientation. Here is an example of what i mean:

  • UINavigationController
    • UiViewController1 (Locked in Portrait) Initial view controller with a button placed on the navigation bar that give to the user the possibility to access to a lists where can be selected other views
    • UIViewController2 (Locked in Landscape)
    • UiViewController3 (Portrait and Landscape)
    • UiViewController4 (Portrait and Landscape)
    • ...
    • ...

How Can i do that?

Gathering answered 1/9, 2014 at 12:59 Comment(2)
check this one #28939160Ofeliaofella
Check this out: https://mcmap.net/q/372153/-objective-c-ios-9-lock-viewcontroller-portrait-orientation it's in Objective C but you will get an idea.Euraeurasia
O
53

According to the Swift Apple Docs for supportedInterfaceOrientations:

Discussion

When the user changes the device orientation, the system calls this method on the root view controller or the topmost presented view controller that fills the window. If the view controller supports the new orientation, the window and view controller are rotated to the new orientation. This method is only called if the view controller's shouldAutorotate method returns true.

Your navigation controller should override shouldAutorotate and supportedInterfaceOrientations as shown below. I did this in a UINavigationController extension for ease:

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

    public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
        return (visibleViewController?.supportedInterfaceOrientations())!
    }
}

And your main viewcontroller (portrait at all times), should have:

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

Then, in your subviewcontrollers that you want to support portrait or landscape:

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

Edit: Updated for iOS 9 :-)

Odyssey answered 12/9, 2014 at 3:25 Comment(12)
I'm facing a problem when views are embedded in a navigation controller, the code doesn't work for example if i want two views, the first one in portrait and the second in landscape, under the same navigation controller.Gathering
In that case, your navigation controller should support all orientations (UIInterfaceOrientationMask.All.toRaw()) and each subview should declare their desired orientation.Odyssey
Just a quick note: toRaw() has been replaced with rawValue in the latest Swift.Ancillary
new function signature is: override func supportedInterfaceOrientations() -> UIInterfaceOrientationMaskCharron
new return value is: return UIInterfaceOrientationMask(rawValue: UIInterfaceOrientationMask.All.rawValue)Charron
As Regis stated above: the new function signature requires a return value of UIInterfaceOrientationMask. This means the correct answer is now: override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask { return UIInterfaceOrientationMask.Portrait }Onesided
I think it don't work in iOS9. Can you please confirm?Ermey
Same problem here @ErmeySingly
I didn't work for me in the first place. The reason is, that my UINavigationController is embedded within a UISplitViewController (which was not so obvious on a compact width device). In this case you have to "apply" the above extension to the UISplitViewController.Guardado
Swift 3 changed these from func to var getters. Just change func to varStevenage
Nice solution - thank you. How do you prevent the following: when I return to the main view after changing to landscape in the subview, the main view also appears in landscape? Only after returning to portrait, the main view "snaps" back into the forced orientation.Sruti
Similarly, when I lock the subview to landscape, it still opens in portrait and only snaps in after changing the device orientation.Sruti
S
24

Also answered [here]

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 or relying on inheritance.

Swift 3

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

}

Then in the desired ViewController you want to lock orientations:

 override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(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)
}
Softy answered 9/2, 2017 at 16:25 Comment(8)
Please don't add the same answer to multiple questions. Answer the best one and flag the rest as duplicates. See Is it acceptable to add a duplicate answer to several questions?Aphrodite
This is the best answer I could have spotted easily. Only answer that worked for me in Swift 3.Dried
Rest of the answers are time waste for latest developers.Dried
@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?Softy
Might be iOS9 vs 10? I have an iPad 3 running iOS9, and this code works, but it does not work on an iPad mini 4 running 10.3.1Selfrevealing
@NileshPol Go to Target => General and check "Requires full screen" It should workSisterhood
@Sisterhood That sounds like it disables split screen and slideover for your app.Solenoid
@Solenoid "Requires full screen" will disable split screen if your app currently uses it, but it will not prevent other apps from sliding over your app. If 'Requires full screen' setting from 'General' is enabled, it will allow all orientation delegate methods shouldAutorotate, preferredInterfaceOrientation, and supportedInterfaceOrientations to fire.Softy
B
15

If your view is embedded in navigationcontroller in storyboard set the navigation controller delegate UINavigationControllerDelegate and add the following method

class ViewController: UIViewController, UINavigationControllerDelegate {
    func navigationControllerSupportedInterfaceOrientations(navigationController: UINavigationController) -> UIInterfaceOrientationMask {
        return UIInterfaceOrientationMask.Portrait
    }
}
Bil answered 11/9, 2015 at 9:23 Comment(0)
K
9

Update: if you're having trouble to set orientation right after the app launches in iOS 10, try do it in ObjC instead of Swift, and with class MyNavigationController: MyNavigationControllerBase:

@implementation ABNavigationControllerBase
- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}
@end

Swift 3:

class MyNavigationController: UINavigationController {
    override func viewDidLoad() {
        super.viewDidLoad()
        UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
    }
    override var shouldAutorotate: Bool {
        return false
    }
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return .portrait
    }
    override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
        return .portrait
    }
}
Kristy answered 26/1, 2017 at 19:21 Comment(1)
The only Swift 3 / iOS 10 solution that worked for me. Thx !Carolus
A
8

Same JasonJasonJason answer in Swift 4.2+ (It worked correctly with iOS 11)

1- Override shouldAutorotate and supportedInterfaceOrientations as shown below.

extension UINavigationController {

open override var shouldAutorotate: Bool {
    return true
}

open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return (visibleViewController?.supportedInterfaceOrientations)!
    }
}

2- And your main viewcontroller (portrait at all times), should have:

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

3- Then, in your subviewcontrollers that you want to support portrait or landscape:

 public override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.all
}
Anything answered 11/1, 2018 at 10:33 Comment(0)
T
7

The important think is to describe supported interface orientations for whole application in AppDelegate. For example to block all views to portrait just do this:

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

Lots of people are looking just for this answer by googling threw this question so I hope you can excuse me.

Works in Swift 3.0

Triglyph answered 24/9, 2016 at 10:43 Comment(1)
Works great! Thank youRishi
C
4

In the main controller where you want portrait,

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

self.orientation = UIInterfaceOrientationMaskPortrait;
//Or self.orientation = UIInterfaceOrientationPortraitUpsideDown
}

and in subVC where you want Landscape use

  self.orientation =  UIInterfaceOrientationLandscapeLeft:
   self.orientation =  UIInterfaceOrientationLandscapeRight:

or you can override this method

- (NSUInteger)supportedInterfaceOrientations
{
  return UIInterfaceOrientationMaskPortrait;
} 

This is how i would do it with Obj-c in iOS7, i think this code would work in iOS8 too

Cornejo answered 1/9, 2014 at 14:10 Comment(3)
I can not access to self.orientationGathering
I wrote this snippet with ios7 in mind, if you are coding in swift there must be something equivalent to this. If that is the case go through the Apple documentation.Cornejo
I can understand your problem, but swift is just a wrapper class of Obj-c, go through the equivalent swift code for this obj-c snippet, Try searching for, Screen orientation in swift documentationCornejo
G
4

Edited for swift2.0 :

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

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    return [.Portrait, .PortraitUpsideDown]
}
Gatewood answered 2/3, 2016 at 12:52 Comment(1)
Swift 4 use - override var supportedInterfaceOrientations : UIInterfaceOrientationMaskSoup
H
3

Here is the Swift Update :-

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.Portrait
}
Heartily answered 25/1, 2016 at 4:39 Comment(2)
would you add some explanation for this? Where, when, how, etc?Murchison
Swift 4 use - override var supportedInterfaceOrientations : UIInterfaceOrientationMaskSoup
P
3

This is the syntax for Swift 3 (XCode 8.2 beta), where these methods where converted to properties:

extension UINavigationController {
    override open var shouldAutorotate: Bool {
        return false
    }
}

class ViewController: UIViewController {

    /*
            ...
    */

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return UIInterfaceOrientationMask.portrait
    }
}
Pallbearer answered 15/12, 2016 at 21:56 Comment(4)
this locks all screen to portrait. How to lock only 1 screen or only specific screens ?Dried
Have you try by overriding this method for each individual ViewController (you can use several).Pallbearer
No. What I am asking is how the above solution can fit to just 1 single screen ?Dried
That's what I'm trying to answer: each screen can have a view controller, so try (I did not try it) overloading supportedInterfaceOrientations differently on each view controller on your app. If it does not work, you can post a new question too.Pallbearer
S
2

This requires two things

  • Informing the controller of its support for rotation.
  • Enforcing rotation and then handing over responsibility to a controller that knows its support for rotation.

Declare an extension on view controller that forces orientation to portrait.

extension UIViewController {

  func forcePortrait() {
    UIView.setAnimationsEnabled(false)
    UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
    UIView.setAnimationsEnabled(true)
  }

}

Any view controller that is locked to portrait could inherit traits.

class PortraitViewController: UIViewController {

  override open var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .portrait }
  override open var shouldAutorotate: Bool { return false }

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    forcePortrait()
  }

}

Any view controller that is capable of rotating between portrait and landscape can inherit those traits.

class LandscapeViewController: UIViewController {

  override open var supportedInterfaceOrientations: UIInterfaceOrientationMask { return [.landscape, .portrait]  }
  override open var shouldAutorotate: Bool { return true }

  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    // if leaving for a portrait only screen, force portrait.
    // forcePortrait()
  }

}

If your landscape view controller is about to segue to a portrait locked screen. Be sure to lock the orientation just before leaving. Then rely on the portrait view controller to enforce its own lack of rotation.

Surtax answered 22/1, 2020 at 0:35 Comment(0)
C
2

Here is the working code to lock the orientation:

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    if UIDevice.current.userInterfaceIdiom == .phone {
        return .allButUpsideDown
    } else {
        return .all
    }
}

More information:

https://www.hackingwithswift.com/example-code/uikit/how-to-lock-a-view-controllers-orientation-using-supportedinterfaceorientations

Carbonari answered 26/1, 2020 at 21:47 Comment(1)
I added this to my uihosting controller and it does nothing for swiftui view. .landscapeGeaghan
A
0

You do have to apply this to your top view controller. However, you can do it in a clean/easy way by subclassing your top view controller and setting a variable within it that to references every time you make a call to:

shouldAutoRotate()

which is called when the device detects an orientation change and precedes the call to:

supportedInterfaceOrientations()//this is only called if shouldAutoRotate() returns true

For example, say my top view controller is a TabBarController:

class SomeSubclassTabBarViewController: UITabBarController { //This subclass allows us to pass along data to each of the tabBars

var dataArray = StoredValues()//also useful for passing info between tabs
var shouldRotate: Bool = false


override func shouldAutorotate() -> Bool { //allow the subviews accessing the tabBarController to set whether they should rotate or not
    return self.shouldRotate
}

}

Then within the view which should have the ability to rotate the screen, set the viewWillAppear() and viewWillDisappear() like so:

override func viewWillAppear(animated: Bool) { //set the rotate capability to true
    let sharedTabBarController = self.tabBarController as SomeSubclassTabBarViewController
    sharedTabBarController.shouldRotate = true
}

override func viewWillDisappear(animated: Bool) {
    let sharedTabBarController = self.tabBarController as SomeSubclassTabBarViewController
    sharedTabBarController.shouldRotate = false
}

and just in case your app crashes while on this screen, it's probably a good idea to explicitly set the shouldRotate: Bool value on each of your views within the viewWillLoad() method.

Alloway answered 24/2, 2015 at 23:59 Comment(0)
B
0

In iOS8, if you want to lock some especific ViewController, create an extension of UINavigationController (in the case you use it):

extension UINavigationController {
public override func shouldAutorotate() -> Bool {
if visibleViewController is YourViewController {
    return false
}
return true
}}
Bailee answered 26/2, 2016 at 1:30 Comment(0)
C
0

If your iOS application is in Landscape mode only and You want to use camera in landscape mode of application then please try for below solution.

Step 1:

In your appdelegate.m class

-(UIInterfaceOrientationMask)application:(UIApplication *)application
supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone){
        NSString *captionVal = [TMUtils getValueInUserDefault:@"CAPTION"];
        if ([captionVal isEqualToString:@"Camera"]) {
            return UIInterfaceOrientationMaskPortrait;
        }else{
            return UIInterfaceOrientationMaskLandscapeRight;
        }
    }else{
        return UIInterfaceOrientationMaskAllButUpsideDown;
    }
}

Here you can take shared preference value CAPTION as keyValue pair and store the value "Camera".

Step 2: Now in your viewController.m class in camera button Action set shared preference value and open new ViewController which will be having camera functionality.

[TMUtils setValueInUserDefault:@"CAPTION" value:@"Camera"];

Step 3: In Camera functionality viewController.m class set storyboard with UIImageView and back button.

Now in ViewDidLoad of camera functionality viewController.m set

- (void)viewDidLoad {
    [super viewDidLoad];

    if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
            NSLog(@"Error");
        } else {

            UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
            pickerController.modalPresentationStyle = UIModalPresentationCurrentContext; //this will allow the picker to be presented in landscape
            pickerController.delegate = self;
            pickerController.allowsEditing = YES;
            pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
            [self presentViewController:pickerController animated:YES completion:nil];
        }
}

Now in UIImagePickerController delegate method set image to UIImageView

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

        UIImage *chosenImage = info[UIImagePickerControllerOriginalImage];
        self.cameraImage.image = chosenImage;
        [picker dismissViewControllerAnimated:YES completion:NULL];
}

Now on camera back UIButton

- (IBAction)cameraBtnAction:(id)sender {
    [TMUtils setValueInUserDefault:@"CAPTION" value:@"NOCamera"];
    [self dismissViewControllerAnimated:YES completion:nil];
}

It will always check according to shared preference value in delegate class function for supportedInterfaceOrientationsForWindow in reference to that value it allow camera functionality ViewController to open camera in Portrait mode and rest it will again go back to Landscape mode, which will completely work fine.

Campaign answered 25/6, 2019 at 11:47 Comment(0)
I
0

When UIKit detects a change in device orientation, it uses the UIApplication object and the root view controller to determine whether the new orientation is allowed. If both objects agree that the new orientation is supported, then auto-rotation occurs. Otherwise, the orientation change is ignored.

By default, the UIApplication object sources its supported interface orientations from the values specified for the UISupportedInterfaceOrientations key in the applications' Information Property List. You can override this behavior by implementing the application:supportedInterfaceOrientationsForWindow: method in your application's delegate. The supported orientation values returned by this method only take effect after the application has finished launching. You can, therefore, use this method to support a different set of orientations after launch.

Allowing your app to rotate into portrait after launch.

- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
    return UIInterfaceOrientationMaskAllButUpsideDown;
}

source: https://developer.apple.com/

Inhaul answered 26/1, 2020 at 18:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.