Como detectar mudança de orientação?


148

Estou usando o Swift e quero poder carregar um UIViewController quando eu rodar para paisagem, alguém pode me apontar na direção certa?

Não consigo encontrar nada online e um pouco confuso com a documentação.


1
Suponho que a API não tenha sido alterada, portanto deve ser "didRotateToOrientation" e "willRotateToOrientation", algo assim, consulte a documentação da Apple
David 'mArm' Ansermot

1
Olá @ mArm.ch, obrigado pela resposta rápida! Então, como eu implementaria isso? (Este é o meu primeiro aplicativo ... Eu sou muito novo para IOS) :)
David

Repostei como resposta, para outras pessoas. Você pode aceitar se estiver tudo bem para você?
David 'mArm' Ansermot

Respostas:


194

Aqui está como eu consegui:

No AppDelegate.swiftinterior da didFinishLaunchingWithOptions função Coloquei:

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

e depois dentro da classe AppDelegate, coloquei a seguinte função:

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

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

Espero que isso ajude mais alguém!

Obrigado!


5
Tentei adicionar o addObserver no AppDelegate, mas obtive um SIGABRT no CoreFoundation com um seletor não reconhecido. No entanto, quando mudei o addObserver para um viewDidLoad na minha primeira visualização, ele funcionou perfeitamente. Apenas para obter informações, se alguém se deparar com o mesmo problema.
FractalDoctor 14/11

1
Sou novo na codificação, mas não deveria selectorter um formato de string de "rotated:"??
Camaleão

4
Im isso é muito certo se você está aceitando os argumentos (que rotated()não)
David

26
Tenha cuidado, pois UIDeviceOrientationé algo diferente de UIInterfaceOrientation.. Isso ocorre porque UIDeviceOrientationtambém detecta orientações voltadas para baixo e para cima, o que significa que seu código pode pular entre retrato e paisagem aleatoriamente se o dispositivo estiver apoiado em uma superfície quase plana, mas levemente irregular (ou seja, balançando na saliência). camera of the 6 / 6s)
liamnichols

4
Este método NÃO FUNCIONA se o telefone desativou a rotação 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
Isso será chamado ANTES da rotação. Se você precisar, por exemplo, do tamanho do quadro após a rotação, esta solução não funcionará.
Aprovado

não funcionou em extensão. paisagem ou retrato ainda imprimem "portraight"
TomSawyer 23/15

4
Este método NÃO FUNCIONA se o telefone desativou a rotação automática.
chengsam

5
no iPad, como a classe de tamanho é a mesma regular para paisagem e retrato, esse método nunca é chamado.
Jacky

Isso também falhará se o dispositivo retornar isFlat. developer.apple.com/documentation/uikit/uideviceorientation/…
CodeBender 17/18

57

Preciso detectar a rotação ao usar a câmera AVFoundatione constatou que os métodos didRotate( agora obsoletos ) e willTransitionnão eram confiáveis ​​para minhas necessidades. O uso da notificação postada por David funcionou, mas não é atual para o Swift 3.x / 4.x.

Swift 4.2 O nome da notificação foi alterado.

O valor do fechamento permanece o mesmo do 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 a notificação para o Swift 4.2 :

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

Para remover a notificação do Swift 4.2 :

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

Em relação à declaração de reprovação , meu comentário inicial foi enganoso, então eu queria atualizá-la. Como observado, o uso de @objcinferência foi descontinuado, o que, por sua vez, era necessário para usar a #selector. Ao usar um fechamento, isso pode ser evitado e agora você tem uma solução que deve evitar uma falha devido à chamada de um seletor inválido.

Tudo abaixo é obsoleto a partir do XCode 10 e iOS 4.2

Swift 4.0 Com o Swift 4.0, a Apple nos incentivou a evitar o uso #selector, portanto essa abordagem usa um bloco de conclusão agora. Essa abordagem também é compatível com o Swift 3.x e seria a abordagem recomendada daqui para frente.

Este é o aviso do compilador que você receberá em um projeto Swift 4.x se você usar a #selectorfunção devido à descontinuação da @objcinferência:

insira a descrição da imagem aqui

Entrada em rápida evolução sobre essa mudança .

Configure o retorno de chamada:

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

Configure a notificação:

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

Rasgue-o:

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

4
Você faz referência a onde a Apple está falando sobre desencorajar o uso do #selector no Swift 4? Eu gostaria de ler por que eles estão dizendo isso.
jeffjv

@jeffjv Certamente, não tenho um link direto para um documento da Apple, mas incluí uma captura de tela do aviso do compilador que o XCode fornece se você usar a abordagem anterior.
CodeBender 7/11

1
Eu adicionei um link para a rápida evolução que discute a mudança.
CodeBender 28/03

1
@ CodeBender: O aviso do compilador não significa o que você sugere. #selector não está sendo depreciado, apenas inferência "@objc". Isso significa que, ao usar uma função como um #seletor, é necessário marcá-la explicitamente, para que o compilador gere o código correto adicional, porque o compilador não tentará mais inferir isso do seu uso. Portanto, se você adicionar "@obj" à sua função rotate () na sua solução Swift 3.0, o código será compilado sem o aviso.
Mythlandia

Obrigado @Mitlandia, atualizei a resposta para resolver a confusão sobre minha declaração inicial.
CodeBender 17/09/18

19

Usando -orientation propriedade de UIDevicenão está correto (mesmo que possa funcionar na maioria dos casos) e pode levar a alguns erros, por exemplo, UIDeviceOrientationconsidere também a orientação do dispositivo se estiver com a face para cima ou para baixo, não há par direto na UIInterfaceOrientationenumeração para aqueles valores.
Além disso, se você bloquear seu aplicativo em alguma orientação específica, o UIDevice fornecerá a orientação do dispositivo sem levar isso em conta.
Por outro lado, o iOS8 substituiu a interfaceOrientationpropriedade na UIViewControllerclasse.
Existem 2 opções disponíveis para detectar a orientação da interface:

  • Use a orientação da barra de status
  • Use classes de tamanho, no iPhone, se não forem substituídas, elas poderão fornecer uma maneira de entender a orientação atual da interface

O que falta ainda é uma maneira de entender a direção de uma mudança na orientação da interface, que é muito importante durante as animações.
Na sessão da WWDC 2014 "Ver avanço do controlador no iOS8", o alto-falante também fornece uma solução para esse problema, usando o método que substitui -will/DidRotateToInterfaceOrientation.

Aqui a solução proposta foi parcialmente implementada, mais informações aqui :

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

Sei que essa pergunta é válida Swift, mas como é um dos principais links para uma pesquisa no Google e se você estiver procurando o mesmo código em 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, isso funciona no iOS8 e 9 / Swift 2 / Xcode7, basta colocar esse código dentro do seu viewcontroller.swift. Ele imprimirá as dimensões da tela a cada mudança de orientação; você pode colocar seu próprio código:

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

5
Esta função foi descontinuada, use "viewWillTransitionToSize: withTransitionCoordinator:" em vez disso
Masa S-AiYa 29/16/16

Além de ser preterido, didRotateFromInterfaceOrientation()não funciona de maneira confiável. Falta algumas rotações. iewWillTransitionToSize:withTransitionCoordinator:funciona ok.
Andrej

@Andrej Muitas coisas agora estão obsoletas, graças a Swift 3
Josh


9

Gosto de verificar a notificação de orientação, porque você pode adicionar esse recurso a qualquer classe, não precisa ser uma visão ou um controlador de visão. Mesmo no seu delegado do aplicativo.

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)

do que armazenar em cache a notificação

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

8

No objetivo C

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

Em rápido

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

Substitua esse método para detectar a mudança de orientação.


8

Swift 3 | Notificação UIDeviceOrientationDidChange observada com muita frequência

O código a seguir imprime "deviceDidRotate" toda vez que seu dispositivo altera a orientação no espaço 3D - independentemente de uma alteração da orientação retrato para paisagem. Por exemplo, se você segurar o telefone na orientação retrato e inclinar para frente e para trás - o deviceDidRotate () será chamado repetidamente.

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

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

Para contornar isso, você pode manter a orientação anterior do dispositivo e verificar se há uma alteração no 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")
}

Ou você pode usar uma notificação diferente, que só é chamada quando o dispositivo muda de paisagem para retrato. Nesse caso, você deseja usar a UIApplicationDidChangeStatusBarOrientationnotificação.

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

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

6

Implementação completa de como detectar alterações de orientação no Swift 3.0.

Eu escolhi usar esta implementação, porque as orientações de telefone de face upe face downforam importantes para mim, e eu queria que o fim de alterar apenas uma vez eu sabia que a orientação estava na posição 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")
        }
    }

}

As peças importantes a serem observadas são:

  1. Você ouve o fluxo de notificação DeviceOrientationDidChange e o vincula à função deviceOrientationDidChange
  2. Em seguida, você liga a orientação do dispositivo. Observe que há unknownorientações algumas vezes.
  3. Como qualquer notificação, antes que o viewController seja inicializado, pare de observar o fluxo de notificações.

Espero que alguém ache isso útil.


Obrigado, ótimo
Mohammad Razipour 13/17

Por isso, estou confuso, atualmente todas as minhas visualizações são ajustadas com base em retrato para paisagem, mas isso não muda se o dispositivo estiver voltado para cima? Como eu usaria o código acima para obter o mesmo efeito quando estiver voltado para cima !?
Famic Tech

Você pode dar um pouco mais de contexto? Não sei ao que efeito você está se referindo. Este código funciona para simplesmente detectar orientação. Se você estiver usando vários métodos para detectar a orientação, poderá ter 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)")
}

Resposta mais limpa. Trabalhou para mim.
Dorad

Isso está obsoleto #
Aziz Javed 22/02

5

Se você quiser fazer algo APÓS a rotação estar concluída, use o UIViewControllerTransitionCoordinatormanipulador de conclusão como este

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 o iOS 8, essa é a maneira correta de fazer isso.

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 se a rotação mudou com: viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)

Com o coordinator.animateAlongsideTransition(nil) { (UIViewControllerTransitionCoordinatorContext) você pode verificar se a transição está concluída.

Veja o código abaixo:

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

Aqui está uma maneira fácil de detectar a orientação do 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
willRotateestá obsoleto agora, melhor uso viewWillTransition.
antes

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

Muitas respostas não ajudam quando é necessário detectar vários controladores de exibição. Este faz o truque.


Você também precisa verificar se o dispositivo foi girado enquanto o controlador de exibição desapareceu (no caso de outro controlador de exibição ser aberto acima da corrente). Na verdade, você pode pular viewWillDisappeare adicionar addObserverem viewDidLoad. O cancelamento da inscrição no iOS exibe o controlador automaticamente.
Alexander Volkov

você esqueceuUIDevice.current.orientation.isFlat
user924 14/19

3

Minha abordagem é semelhante ao que o bpedit mostra acima, mas com um foco no iOS 9+. Eu queria alterar o escopo do FSCalendar quando a exibição 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)
}

Isso abaixo funcionou, mas eu me senti envergonhado com isso :)

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

Eu uso UIUserInterfaceSizeClasspara detectar uma orientação alterada em uma UIViewControllerclasse assim:

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

você esqueceu UIDevice.current.orientation.isFlat
user924 14/19

1

Swift 5

Classe de configuração para receber notificações de alterações na orientação do 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 do manipulador de instalação:

@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: Apenas use este código para identificar o UIViewController em qual orientação


Seu código não está funcionando no projeto. também preciso fazer mais alguma coisa?
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

No iOS 13.1.2, a orientação sempre retorna 0 até que o dispositivo seja girado. Preciso chamar UIDevice.current.beginGeneratingDeviceOrientationNotifications () para obter a rotação real antes que ocorra qualquer evento de rotação.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.