使用 Swift 在一个 ViewController 中实现力景观模式

我试图强迫只有一个视图在我的应用程序的景观模式, 我打电话来:

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
}

视图在 肖像画模式下启动,当我改变设备方向时保持旋转。从不调用 shouldAutorotate()方法。

如果你能帮忙,我将不胜感激。

103291 次浏览

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

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
}

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;
}
}

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.

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
}

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
}
*/
...
}
class CustomUIViewController : UIViewController{


override var   supportedInterfaceOrientations : UIInterfaceOrientationMask{


return  .landscapeLeft


}


}




class ViewController: CustomUIViewController {
.
.
.
}

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

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. :)

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


}

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.

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

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 }

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

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
}

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

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
}




}

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

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...
}