Cómo bloquear la orientación de un controlador de vista al modo vertical solo en Swift

Desde que mi aplicación recibió soporte para toda orientación. Me gustaría bloquear solo el modo vertical al UIViewController específico.

(Por ejemplo, supongamos que se trata de una aplicación con tabs y cuando la vista de inicio de sesión aparece de forma modal, solo quiero que la vista de inicio de sesión esté en modo retrato, independientemente de cómo gire el dispositivo o de la orientación actual del dispositivo)

Las cosas pueden ser bastante complicadas cuando tienes una jerarquía de vistas complicada, como tener múltiples controladores de navegación y / o controles de vista de tabs.

Esta implementación lo coloca en los controladores de vista individuales para establecer cuándo les gustaría bloquear las orientaciones, en lugar de confiar en el delegado de la aplicación para encontrarlos al recorrer las subvistas.

Swift 3 y 4

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

En alguna otra clase struct o helper global, aquí creé 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") } } 

Luego, en el ViewController deseado, desea bloquear las orientaciones:

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

Si iPad o Universal App

Asegúrese de que “Requiere pantalla completa” esté marcada en Configuración de destino -> General -> Información de implementación. supportedInterfaceOrientationsFor delegate no se llamará si eso no está marcado. enter image description here

Agregue este código para forzar el retrato y bloquearlo:

 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 } 

En su AppDelegate: configure supportedInterfaceOrientationsForWindow con las orientaciones que desee que admita la aplicación completa:

 func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask { return UIInterfaceOrientationMask.All } 

Un montón de excelentes respuestas en este hilo, pero ninguna coincidió exactamente con mis necesidades. Tengo una aplicación con tabs con controladores de navegación en cada pestaña, y una vista necesaria para rotar, mientras que las otras necesitan estar encerradas en retrato. El controlador de navegación no estaba redimensionando sus subvistas correctamente, por alguna razón. Encontré una solución (en Swift 3) al combinar con esta respuesta, y los problemas de diseño desaparecieron. Crea la estructura como sugiere @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") } } 

Entonces subclase 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 } } 

No olvides cambiar la clase del TabBarController en el guión gráfico a la subclase recién creada.

Esta es una solución genérica para su problema y otros relacionados.

1. Cree la clase auxiliar UIHelper y aplique los siguientes métodos:

  /**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. Cree un protocolo con su comportamiento de deseo, para su caso específico será retrato.

protocolo orientationIsOnlyPortrait {}

Nota: si lo desea, agréguelo en la parte superior de la Clase UIHelper.

3. Extiende tu Controlador de Vista

En tu caso:

 class Any_ViewController: UIViewController,orientationIsOnlyPortrait { .... } 

4. En la clase de delegado de aplicaciones, agregue este método:

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

Notas finales:

  • Si esa clase más está en modo vertical, solo extienda ese protocolo.
  • Si desea otros comportamientos de los controladores de vista, cree otros protocolos y siga la misma estructura.
  • Este ejemplo soluciona el problema con los cambios de orientaciones después de los controladores de vista push

Swift 4

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

Su ViewController Agregue la siguiente línea si solo necesita orientación vertical. tiene que aplicar esto a todos ViewController necesita mostrar el modo de retrato.

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

y eso hará que la orientación de la pantalla para otros ViewController de acuerdo con la orientación física del dispositivo.

 override func viewWillDisappear(_ animated: Bool) { AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.all) } 

Para establecer la orientación horizontal en todas las vistas de su aplicación y permitir solo una vista para todas las orientaciones (para poder agregar rollo de cámara, por ejemplo):

En AppDelegate.swift:

 var adaptOrientation = false 

En: didFinishLaunchingWithOptions

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

En otro lugar en 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 } } 

Luego, en la vista que segue a la que desea poder tener todas las orientaciones:

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

Lo último es marcar Orientación del dispositivo: – Retrato – Paisaje a la izquierda – Paisaje a la derecha

A partir de iOS 10 y 11, iPad admite Slide Over y Split View. Para habilitar una aplicación en Slide Over y Split View, Requires full screen debe estar desmarcada. Eso significa que la respuesta aceptada no se puede utilizar si la aplicación desea admitir Slide Over y Split View. Vea más sobre las mejoras en la adopción de tareas múltiples de Apple en iPad aquí .

Tengo una solución que permite (1) desmarcar Requires full screen , (2) solo una función para implementar en appDelegate (especialmente si no quiere / no puede modificar los controladores de vista de destino), y (3) evitar llamadas recursivas. No hay necesidad de clase auxiliar ni extensiones.

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

Editar

@bmjohns señaló que esta función no se llama en iPad. Lo verifiqué y sí, no fue llamado. Entonces, hice un poco más de prueba y descubrí algunos hechos:

  1. Desmarqué Requires full screen porque quiero habilitar Slide Over y Slide View en iPad. Esto requiere que la aplicación sea compatible con la orientación 4 para iPad, en Info.plist: Supported interface orientations (iPad) .

Mi aplicación funciona de la misma manera que Facebook: en iPhone, la mayoría de las veces está bloqueada en vertical. Al ver la imagen en pantalla completa, permite a los usuarios rotar el paisaje para una mejor visualización. En iPad, los usuarios pueden rotar a cualquier orientación en cualquier controlador de vista. Por lo tanto, la aplicación se ve bien cuando el iPad está de pie en Smart Cover (paisaje a la izquierda).

  1. Para que el iPad llame a la application(_:supportedInterfaceOrientationsFor) , en Info.plist, solo mantenga el retrato para iPad. La aplicación perderá la habilidad Slide Over + Split View. Pero puede bloquear o desbloquear la orientación de cualquier controlador de vista, en un solo lugar y sin necesidad de modificar la clase ViewController.

  2. Finalmente, esta función se invoca en el ciclo de vida del controlador de visualización, cuando se visualiza / elimina la vista. Si su aplicación necesita bloquear / desbloquear / cambiar de orientación en otro momento, es posible que no funcione

bmjohns -> Eres mi salvador de vida. Esa es la única solución de trabajo (con la estructura AppUtility)

Creé esta clase:

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

y siguió sus instrucciones, y todo funciona perfectamente para Swift 3 -> xcode versión 8.2.1

Gracias a la respuesta de @ bmjohn anterior. Aquí hay una versión Xamarin / C # que funciona del código de esa respuesta, para guardar otras el tiempo de transcripción:

AppDelegate.cs

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

Clase Static OrientationUtility.cs:

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

Controlador de vista:

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

Crear una nueva extensión con

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