¿Cómo detectar el cambio de orientación?


148

Estoy usando Swift y quiero poder cargar un UIViewController cuando giro a horizontal, ¿alguien puede señalarme en la dirección correcta?

No puedo encontrar nada en línea y la documentación me confunde un poco.


1
Supongo que la API no ha cambiado, por lo que debería ser "didRotateToOrientation" y "willRotateToOrientation", algo así, eche un vistazo a la documentación de Apple
David 'mArm' Ansermot

1
Hola @ mArm.ch, gracias por la rápida respuesta! Entonces, ¿cómo implementaría esto? (Esta es mi primera aplicación ... Soy muy nuevo en iOS) :)
David

Publiqué como respuesta, para otras personas. ¿Puedes aceptarlo si está bien para ti?
David 'mArm' Ansermot

Respuestas:


194

Así es como lo hice funcionar:

En AppDelegate.swiftel interior de la didFinishLaunchingWithOptions función pongo:

NotificationCenter.default.addObserver(self, selector: #selector(AppDelegate.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)

y luego dentro de la clase AppDelegate pongo la siguiente función:

func rotated() {
    if UIDeviceOrientationIsLandscape(UIDevice.current.orientation) {
        print("Landscape")
    }

    if UIDeviceOrientationIsPortrait(UIDevice.current.orientation) {
        print("Portrait")
    }
}

Espero que esto ayude a alguien más!

¡Gracias!


55
Intenté agregar addObserver en AppDelegate pero seguí obteniendo un SIGABRT en CoreFoundation con un selector no reconocido. Sin embargo, cuando moví addObserver a viewDidLoad en mi primera vista, funcionó perfectamente. Solo para información si alguien se encuentra con el mismo problema.
FractalDoctor

1
Soy nuevo en la codificación pero no debería selectortener un formato de cadena de "rotated:"??
Camaleón

44
Estoy bastante seguro de que solo si está aceptando argumentos (que rotated()no lo hace)
David

26
Tenga cuidado porque UIDeviceOrientationes algo diferente a UIInterfaceOrientation... Esto UIDeviceOrientationtambién se debe a que detecta las orientaciones boca abajo y boca arriba, lo que significa que su código puede saltar aleatoriamente entre retrato y paisaje si su dispositivo descansa sobre una superficie casi plana pero ligeramente irregular (es decir, balanceándose sobre el saliente cámara de los 6 / 6s)
liamnichols

44
Este método NO FUNCIONA si el teléfono desactiva la rotación automática.
chengsam

178
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    }
    if UIDevice.current.orientation.isFlat {
        print("Flat")
    } else {
        print("Portrait")
    }
}

71
Esto se llamará ANTES de la rotación. Si necesita, por ejemplo, el tamaño del cuadro después de la rotación, esta solución no funcionará.
Voto a favor del

No funcionó en extensión. paisaje o retrato todavía imprimen "portraight"
TomSawyer

44
Este método NO FUNCIONA si el teléfono desactiva la rotación automática.
chengsam

55
en iPad, dado que la clase de tamaño es la misma para el paisaje y el retrato, este método nunca se llama.
Jacky

Esto también fallará si el dispositivo devuelve isFlat. developer.apple.com/documentation/uikit/uideviceorientation/…
CodeBender

57

Necesito detectar la rotación mientras uso la cámara AVFoundationy descubrí que los métodos didRotate( ahora obsoletos ) willTransitionno eran confiables para mis necesidades. El uso de la notificación publicada por David funcionó, pero no está actualizado para Swift 3.x / 4.x.

Swift 4.2 El nombre de la notificación ha sido cambiado.

El valor de cierre sigue siendo el mismo que Swift 4.0:

var didRotate: (Notification) -> Void = { notification in
        switch UIDevice.current.orientation {
        case .landscapeLeft, .landscapeRight:
            print("landscape")
        case .portrait, .portraitUpsideDown:
            print("Portrait")
        default:
            print("other")
        }
    }

Para configurar la notificación para Swift 4.2 :

NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification,
                                       object: nil,
                                       queue: .main,
                                       using: didRotate)

Para eliminar la notificación de Swift 4.2 :

NotificationCenter.default.removeObserver(self,
                                          name: UIDevice.orientationDidChangeNotification,
                                          object: nil)

Con respecto a la declaración de desaprobación , mi comentario inicial fue engañoso, por lo que quería actualizar eso. Como se señaló, el uso de la @objcinferencia ha quedado en desuso, lo que a su vez era necesario para usar a #selector. Al usar un cierre, esto se puede evitar y ahora tiene una solución que debería evitar un bloqueo debido a llamar a un selector no válido.

Todo lo que se muestra a continuación es obsoleto a partir de XCode 10 y iOS 4.2

Swift 4.0 Con Swift 4.0, Apple nos ha alentado a evitar el uso de #selector, por lo que este enfoque utiliza un bloque de finalización ahora. Este enfoque también es retrocompatible con Swift 3.xy sería el enfoque recomendado en el futuro.

Esta es la advertencia del compilador que recibirá en un proyecto Swift 4.x si usa la #selectorfunción debido a la depreciación de la @objcinferencia:

ingrese la descripción de la imagen aquí

Entrada en rápida evolución en este cambio .

Configurar la devolución de llamada:

// If you do not use the notification var in your callback, 
// you can safely replace it with _
    var didRotate: (Notification) -> Void = { notification in
        switch UIDevice.current.orientation {
        case .landscapeLeft, .landscapeRight:
            print("landscape")
        case .portrait, .portraitUpsideDown:
            print("Portrait")
        default:
            print("other")
        }
    }

Configurar la notificación:

NotificationCenter.default.addObserver(forName: .UIDeviceOrientationDidChange,
                                       object: nil,
                                       queue: .main,
                                       using: didRotate)

Romper en pedazos:

NotificationCenter.default.removeObserver(self, name: .UIDeviceOrientationDidChange, object: nil)

44
¿Tiene referencia a dónde habla Apple de desalentar el uso de #selector en Swift 4? Me gustaría leer sobre por qué dicen esto.
jeffjv

@jeffjv Ciertamente, no tengo un enlace directo a un documento de Apple, pero sí incluí una captura de pantalla de la advertencia del compilador que XCode proporciona si utiliza el enfoque anterior.
CodeBender el

1
He agregado un enlace a swift-evolution que analiza el cambio.
CodeBender

1
@CodeBender: la advertencia del compilador no significa lo que sugieres. #selector no se está depreciando, solo inferencia "@objc". Esto significa que cuando se usa una función como #selector, debe marcarla explícitamente, de modo que el compilador generará el código correcto adicional porque el compilador ya no intentará inferirlo de su uso. Por lo tanto, si agrega "@obj" a su función rotate () en su solución Swift 3.0, el código se compilará sin la advertencia.
Mythlandia

Gracias @Mythlandia, actualicé la respuesta para resolver la confusión sobre mi declaración inicial.
CodeBender

19

El uso de la -orientationpropiedad de UIDeviceno es correcto (incluso si podría funcionar en la mayoría de los casos) y podría provocar algunos errores, por ejemplo, UIDeviceOrientationconsidere también la orientación del dispositivo si está boca arriba o hacia abajo, no hay par directo en la UIInterfaceOrientationenumeración para aquellos valores.
Además, si bloquea su aplicación en alguna orientación particular, UIDevice le dará la orientación del dispositivo sin tenerlo en cuenta.
Por otro lado, iOS8 ha dejado de usar la interfaceOrientationpropiedad en UIViewControllerclase.
Hay 2 opciones disponibles para detectar la orientación de la interfaz:

  • Usa la orientación de la barra de estado
  • Use clases de tamaño, en iPhone, si no se anulan, podrían darle una manera de comprender la orientación actual de la interfaz

Lo que aún falta es una forma de entender la dirección de un cambio de orientación de la interfaz, que es muy importante durante las animaciones.
En la sesión de WWDC 2014 "Ver avance del controlador en iOS8", el orador también proporciona una solución a ese problema, utilizando el método que reemplaza -will/DidRotateToInterfaceOrientation.

Aquí la solución propuesta parcialmente implementada, más información aquí :

func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        let orientation = orientationFromTransform(coordinator.targetTransform())
        let oldOrientation = UIApplication.sharedApplication().statusBarOrientation
        myWillRotateToInterfaceOrientation(orientation,duration: duration)
        coordinator.animateAlongsideTransition({ (ctx) in
            self.myWillAnimateRotationToInterfaceOrientation(orientation,
            duration:duration)
            }) { (ctx) in
                self.myDidAnimateFromInterfaceOrientation(oldOrientation)
        }
    }

11

Sé que esta pregunta es para Swift, pero dado que es uno de los principales enlaces para una búsqueda en Google y si está buscando el mismo código en Objective-C:

// add the observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rotated:) name:UIDeviceOrientationDidChangeNotification object:nil];

// remove the observer
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];

// method signature
- (void)rotated:(NSNotification *)notification {
    // do stuff here
}

11

Fácil, esto funciona en iOS8 y 9 / Swift 2 / Xcode7, solo ponga este código dentro de su viewcontroller.swift. Imprimirá las dimensiones de la pantalla con cada cambio de orientación, puede poner su propio código en su lugar:

override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
        getScreenSize()
    }
    var screenWidth:CGFloat=0
    var screenHeight:CGFloat=0
    func getScreenSize(){
        screenWidth=UIScreen.mainScreen().bounds.width
        screenHeight=UIScreen.mainScreen().bounds.height
        print("SCREEN RESOLUTION: "+screenWidth.description+" x "+screenHeight.description)
    }

55
Esta función fue desaprobada, use "viewWillTransitionToSize: withTransitionCoordinator:" en su lugar
Masa S-AiYa

Además de ser obsoleto, didRotateFromInterfaceOrientation()no funciona de manera confiable. Se echa de menos algunas rotaciones. iewWillTransitionToSize:withTransitionCoordinator:funciona bien
Andrej

@Andrej Muchas cosas ahora están en desuso gracias a Swift 3
Josh


9

Me gusta verificar la notificación de orientación porque puede agregar esta función en cualquier clase, no es necesario que sea una vista o un controlador de vista. Incluso en su delegado de aplicaciones.

SWIFT 5:

    //ask the system to start notifying when interface change
    UIDevice.current.beginGeneratingDeviceOrientationNotifications()
    //add the observer
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(orientationChanged(notification:)),
        name: UIDevice.orientationDidChangeNotification,
        object: nil)

que almacenar en caché la notificación

    @objc func orientationChanged(notification : NSNotification) {
        //your code there
    }

8

En el objetivo C

-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator

En rápido

func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)

Anule este método para detectar el cambio de orientación.


8

Swift 3 | UIDeviceOrientationDidChange Notificación observada con demasiada frecuencia

El siguiente código imprime "deviceDidRotate" cada vez que su dispositivo cambia de orientación en el espacio 3D, independientemente de un cambio de orientación vertical a horizontal. Por ejemplo, si sostiene su teléfono en orientación vertical y lo inclina hacia adelante y hacia atrás, se llama repetidamente deviceDidRotate ().

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self, 
        selector:  #selector(deviceDidRotate), 
        name: .UIDeviceOrientationDidChange, 
        object: nil
    )
}

func deviceDidRotate() {
    print("deviceDidRotate")
}

Para evitar esto, puede mantener la orientación anterior del dispositivo y verificar si hay un cambio en deviceDidRotate ().

var previousDeviceOrientation: UIDeviceOrientation = UIDevice.current.orientation

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self, 
        selector:  #selector(deviceDidRotate), 
        name: .UIDeviceOrientationDidChange, 
        object: nil
    )
}

func deviceDidRotate() {
    if UIDevice.current.orientation == previousDeviceOrientation { return }
    previousDeviceOrientation = UIDevice.current.orientation
    print("deviceDidRotate")
}

O puede usar una notificación diferente que solo se llama cuando el dispositivo cambia de horizontal a vertical. En este caso, querrás usar la UIApplicationDidChangeStatusBarOrientationnotificación.

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self, 
        selector:  #selector(deviceDidRotate), 
        name: .UIApplicationDidChangeStatusBarOrientation, 
        object: nil
    )
}

func deviceDidRotate() {
    print("deviceDidRotate")
}

6

Implementación funcional completa de cómo detectar cambios de orientación en Swift 3.0.

Elegí usar esta implementación porque las orientaciones telefónicas face upy face downeran importantes para mí, y quería que la vista cambiara solo una vez que supiera que la orientación estaba en la posición especificada.

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        //1
        NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)

    }

    deinit {
        //3
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    }

    func deviceOrientationDidChange() {
        //2
        switch UIDevice.current.orientation {
        case .faceDown:
            print("Face down")
        case .faceUp:
            print("Face up")
        case .unknown:
            print("Unknown")
        case .landscapeLeft:
            print("Landscape left")
        case .landscapeRight:
            print("Landscape right")
        case .portrait:
            print("Portrait")
        case .portraitUpsideDown:
            print("Portrait upside down")
        }
    }

}

Las piezas importantes a tener en cuenta son:

  1. Escucha la secuencia de notificación DeviceOrientationDidChange y la vincula a la función deviceOrientationDidChange
  2. Luego enciende la orientación del dispositivo, asegúrese de tener en cuenta que unknowna veces hay una orientación.
  3. Al igual que cualquier notificación, antes de que se desinicialice viewController, asegúrese de dejar de observar el flujo de notificaciones.

Espero que alguien encuentre esto útil.


Gracias, genial
Mohammad Razipour

Entonces, estoy confundido, actualmente todas mis vistas se ajustan de vertical a horizontal, pero ¿no cambia si el dispositivo está boca arriba? ¿Cómo usaría el código anterior para lograr el mismo efecto cuando está boca arriba?
Famic Tech

¿Puedes dar un poco más de contexto? No estoy seguro de a qué efecto te refieres. Este código funciona simplemente para detectar la orientación. Si está utilizando varios métodos para detectar la orientación, podría tener problemas.
Rob Norback

6
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
    //swift 3
    getScreenSize()
}


func getScreenSize(){
   let screenWidth = UIScreen.main.bounds.width
   let  screenHeight = UIScreen.main.bounds.height
    print("SCREEN RESOLUTION: \(screenWidth.description) x \(screenHeight.description)")
}

La respuesta más limpia. Trabajó para mi.
Dorad

Esto ahora está en desuso
Aziz Javed

5

Si desea hacer algo DESPUÉS de que se complete la rotación, puede usar el UIViewControllerTransitionCoordinatorcontrolador de finalización de esta manera

public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    // Hook in to the rotation animation completion handler
    coordinator.animate(alongsideTransition: nil) { (_) in
        // Updates to your UI...
        self.tableView.reloadData()
    }
}

5

Desde iOS 8, esta es la forma correcta de hacerlo.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    coordinator.animate(alongsideTransition: { context in
        // This is called during the animation
    }, completion: { context in
        // This is called after the rotation is finished. Equal to deprecated `didRotate`
    })
}

4

Verifique si la rotación ha cambiado con: viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)

Con el coordinator.animateAlongsideTransition(nil) { (UIViewControllerTransitionCoordinatorContext)puede verificar si la transición ha finalizado.

Ver código a continuación:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {

    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

    coordinator.animateAlongsideTransition(nil) { (UIViewControllerTransitionCoordinatorContext) in
        // if you want to execute code after transition finished
        print("Transition finished")
    }

    if size.height < size.width {
        // Landscape
        print("Landscape")
    } else {
        // Portrait
        print("Portrait")
    }

}

4

Aquí hay una manera fácil de detectar la orientación del dispositivo: ( Swift 3 )

override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
            handleViewRotaion(orientation: toInterfaceOrientation)
        }

    //MARK: - Rotation controls
    func handleViewRotaion(orientation:UIInterfaceOrientation) -> Void {
        switch orientation {
        case .portrait :
            print("portrait view")
            break
        case .portraitUpsideDown :
            print("portraitUpsideDown view")
            break
        case .landscapeLeft :
            print("landscapeLeft view")
            break
        case .landscapeRight :
            print("landscapeRight view")
            break
        case .unknown :
            break
        }
    }

2
willRotateahora está en desuso, mejor uso viewWillTransition.
antes del

4

Swift 4:

override func viewWillAppear(_ animated: Bool) {
    NotificationCenter.default.addObserver(self, selector: #selector(deviceRotated), name: UIDevice.orientationDidChangeNotification, object: nil)
}

override func viewWillDisappear(_ animated: Bool) {
    NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
}

@objc func deviceRotated(){
    if UIDevice.current.orientation.isLandscape {
        //Code here
    } else {
        //Code here
    }
}

Muchas respuestas no ayudan cuando se necesita detectar en varios controladores de vista. Este hace el truco.


También debe verificar si el dispositivo se giró mientras el controlador de vista desapareció (en caso de que otro controlador de vista se abra por encima de la corriente). De hecho, se puede saltar viewWillDisappeary añadir addObserveren viewDidLoad. iOS cancela la vista del controlador de vista automáticamente.
Alexander Volkov

olvidasteUIDevice.current.orientation.isFlat
usuario924

3

Mi enfoque es similar a lo que muestra bpedit arriba, pero con un enfoque iOS 9+. Quería cambiar el alcance del FSCalendar cuando la vista gira.

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

    coordinator.animateAlongsideTransition({ (context) in
        if size.height < size.width {
            self.calendar.setScope(.Week, animated: true)
            self.calendar.appearance.cellShape = .Rectangle
        }
        else {
            self.calendar.appearance.cellShape = .Circle
            self.calendar.setScope(.Month, animated: true)

        }

        }, completion: nil)
}

Esto funcionó a continuación, pero me sentí avergonzado al respecto :)

coordinator.animateAlongsideTransition({ (context) in
        if size.height < size.width {
            self.calendar.scope = .Week
            self.calendar.appearance.cellShape = .Rectangle
        }
        }) { (context) in
            if size.height > size.width {
                self.calendar.scope = .Month
                self.calendar.appearance.cellShape = .Circle
            }
    }

2

Yo uso UIUserInterfaceSizeClasspara detectar una orientación cambiada en una UIViewControllerclase así:

override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {

    let isiPadLandscapePortrait = newCollection.horizontalSizeClass == .regular && newCollection.verticalSizeClass == .regular
    let isiPhonePlustLandscape = newCollection.horizontalSizeClass == .regular && newCollection.verticalSizeClass == .compact
    let isiPhonePortrait = newCollection.horizontalSizeClass == .compact && newCollection.verticalSizeClass == .regular
    let isiPhoneLandscape = newCollection.horizontalSizeClass == .compact && newCollection.verticalSizeClass == .compact

     if isiPhonePortrait {
         // do something...
     }
}

1

Para Swift 3

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.current.orientation.isLandscape {
        //Landscape
    }
    else if UIDevice.current.orientation.isFlat {
        //isFlat
    }
    else {
        //Portrait
    }
}

olvidasteUIDevice.current.orientation.isFlat
usuario924

1

Swift 5

Configure la clase para recibir notificaciones de cambio de orientación del dispositivo:

class MyClass {

    ...

    init (...) {

        ...

        super.init(...)

        // subscribe to device orientation change notifications
        UIDevice.current.beginGeneratingDeviceOrientationNotifications()
        NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: UIDevice.orientationDidChangeNotification, object: nil)

        ...

    }

    ...

}

Código del controlador de configuración:

@objc extension MyClass {
    func orientationChanged(_ notification: NSNotification) {
        let device = notification.object as! UIDevice
        let deviceOrientation = device.orientation

        switch deviceOrientation {
        case .landscapeLeft:   //do something for landscape left
        case .landscapeRight:  //do something for landscape right
        case .portrait:        //do something for portrait
        case .portraitUpsideDown: //do something for portrait upside-down 
        case .faceDown:        //do something for face down
        case .faceUp:          //do something for face up
        case .unknown:         //handle unknown
        @unknown default:      //handle unknown default
        }
    }
}

0
- (void)viewDidLoad {
  [super viewDidLoad];
  [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(OrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
}

-(void)OrientationDidChange:(NSNotification*)notification {
  UIDeviceOrientation Orientation=[[UIDevice currentDevice]orientation];

  if(Orientation==UIDeviceOrientationLandscapeLeft || Orientation==UIDeviceOrientationLandscapeRight) {
    NSLog(@"Landscape");
  } else if(Orientation==UIDeviceOrientationPortrait) {
    NSLog(@"Potrait Mode");
  }
}

NOTA: Simplemente use este código para identificar que UIViewController está en qué orientación


Tu código no funciona en el proyecto. ¿Necesito hacer algo más también?
Syed Ali Salman

0
override func viewDidLoad() {
    NotificationCenter.default.addObserver(self, selector: #selector(MyController.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
//...
}

@objc
private func rotated() {
    if UIDevice.current.orientation.isLandscape {

    } else if UIDevice.current.orientation.isPortrait {

    }

    //or you can check orientation separately UIDevice.current.orientation
    //portrait, portraitUpsideDown, landscapeLeft, landscapeRight... 

}

0

Con iOS 13.1.2, la orientación siempre devuelve 0 hasta que se gira el dispositivo. Necesito llamar a UIDevice.current.beginGeneratingDeviceOrientationNotifications () para obtener la rotación real antes de que ocurra cualquier evento de rotación.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.