Swift - Como detectar mudanças de orientação

93

Quero adicionar duas imagens para visualização de imagem única (ou seja, para paisagem uma imagem e para retrato outra imagem), mas não sei como detectar mudanças de orientação usando linguagens rápidas.

Eu tentei esta resposta, mas leva apenas uma imagem

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.currentDevice().orientation.isLandscape.boolValue {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

Eu sou novo no desenvolvimento de iOS, qualquer conselho seria muito apreciado!

Raghuram
fonte
1
Você poderia editar sua pergunta e formatar seu código? Você pode fazer isso com cmd + k quando tiver seu código selecionado.
jbehrens94 de
Ele imprime paisagem / retrato corretamente?
Daniel
sim, ele imprime corretamente @simpleBob
Raghuram
Você deve usar a orientação da interface em vez da orientação do dispositivo => stackoverflow.com/a/60577486/8780127
Wilfried Josset

Respostas:

120
let const = "Background" //image name
let const2 = "GreyBackground" // image name
    @IBOutlet weak var imageView: UIImageView!
    override func viewDidLoad() {
        super.viewDidLoad()

        imageView.image = UIImage(named: const)
        // Do any additional setup after loading the view.
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        if UIDevice.current.orientation.isLandscape {
            print("Landscape")
            imageView.image = UIImage(named: const2)
        } else {
            print("Portrait")
            imageView.image = UIImage(named: const)
        }
    }
Rutvik Kanbargi
fonte
Obrigado, funciona para imageView, e se eu quiser adicionar uma imagem de fundo para viewController ??
Raghuram
1
Coloque um imageView no fundo. Atribua restrição ao imageView para a visualização principal, superior, inferior, inicial e posterior do ViewController para cobrir todo o fundo.
Rutvik Kanbargi
4
Não se esqueça de chamar super.viewWillTransitionToSize dentro do seu método de substituição
Brian Sachetta
Você sabe por que não posso substituir viewWillTransitionao criar subclasses UIView?
Xcoder de
2
Existe outro tipo de orientação: .isFlat. Se você deseja apenas pegar portrait, sugiro que altere a cláusula else para else if UIDevice.current.orientation.isPortrait. Observação: .isFlatpode acontecer em ambos, retrato e paisagem, e apenas com o else {} será o padrão sempre (mesmo se for paisagem plana).
PMT
76

Usando NotificationCenter e UIDevice 'sbeginGeneratingDeviceOrientationNotifications

Swift 4.2+

override func viewDidLoad() {
    super.viewDidLoad()        

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

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

func rotated() {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

Swift 3

override func viewDidLoad() {
    super.viewDidLoad()        

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}

deinit {
     NotificationCenter.default.removeObserver(self)
}

func rotated() {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}
maslovsa
fonte
1
Definitivamente - minha abordagem precisa de algum código adicional. Mas ele realmente "detecta mudanças de orientação".
maslovsa
3
@Michael Porque viewWillTransition:antecipa que a mudança acontecerá enquanto using UIDeviceOrientationDidChangeé chamado assim que a mudança for concluída.
Lyndsey Scott
1
@LyndseyScott Ainda acredito que o comportamento descrito pode ser alcançado sem usar o NSNotificationCenter potencialmente perigoso. Reveja a seguinte resposta e diga-me o que pensa: stackoverflow.com/a/26944087/1306884
Michael
4
O que é interessante nessa resposta é que isso dará a orientação atual mesmo quando o controlador de visualização shouldAutorotateestiver definido como false. Isso é útil para telas como a tela principal do aplicativo Câmera - a orientação é bloqueada, mas os botões podem girar.
yoninja
1
A partir do iOS 9, você nem precisa chamar explicitamente deinit. Você pode ler mais sobre isso aqui: useyourloaf.com/blog/…
James Jordan Taylor
63

Código do Swift 3 acima atualizado:

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

    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}
Yaroslav Bai
fonte
1
Você sabe por que não consigo substituir viewWillTransition ao criar uma subclasse de UIView?
Xcoder de
Isso travará se você tentar fazer referência a qualquer uma das visualizações no controlador.
James Jordan Taylor
@JamesJordanTaylor, você poderia explicar por que ele travará?
orium
Eu comentei há 6 meses, mas acho que o motivo dado pelo Xcode quando tentei foi uma exceção de ponteiro nil porque a própria visão ainda não havia sido instanciada, apenas o viewController. Eu poderia estar me lembrando mal.
James Jordan Taylor
@Xcoder esta é uma função no UIViewController e não no UIView - é por isso que você não pode substituí-la.
LightningStryk de
23

⚠️Device Orientation! = Interface Orientation⚠️

Swift 5. * iOS13 e inferior

Você realmente deve fazer a diferença entre:

  • Orientação do dispositivo => Indica a orientação do dispositivo físico
  • Interface Orientation => Indica a orientação da interface exibida na tela

Existem muitos cenários em que esses 2 valores são incompatíveis, como:

  • Quando você bloqueia a orientação da tela
  • Quando você tem seu dispositivo plano

Na maioria dos casos, você deseja usar a orientação da interface e pode obtê-la por meio da janela:

private var windowInterfaceOrientation: UIInterfaceOrientation? {
    return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
}

Caso você também queira oferecer suporte ao <iOS 13 (como o iOS 12), faça o seguinte:

private var windowInterfaceOrientation: UIInterfaceOrientation? {
    if #available(iOS 13.0, *) {
        return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
    } else {
        return UIApplication.shared.statusBarOrientation
    }
}

Agora você precisa definir onde reagir à mudança de orientação da interface da janela. Existem várias maneiras de fazer isso, mas a solução ideal é fazê-lo internamente willTransition(to newCollection: UITraitCollection.

Este método UIViewController herdado, que pode ser sobrescrito, será acionado toda vez que a orientação da interface for alterada. Consequentemente, você pode fazer todas as modificações no último.

Aqui está um exemplo de solução :

class ViewController: UIViewController {
    override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        super.willTransition(to: newCollection, with: coordinator)

        coordinator.animate(alongsideTransition: { (context) in
            guard let windowInterfaceOrientation = self.windowInterfaceOrientation else { return }

            if windowInterfaceOrientation.isLandscape {
                // activate landscape changes
            } else {
                // activate portrait changes
            }
        })
    }

    private var windowInterfaceOrientation: UIInterfaceOrientation? {
        return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
    }
}

Ao implementar este método, você será capaz de reagir a qualquer mudança de orientação de sua interface. Mas lembre-se de que ele não será acionado na abertura do aplicativo, então você também terá que atualizar manualmente sua interface no viewWillAppear().

Criei um projeto de amostra que destaca a diferença entre a orientação do dispositivo e a orientação da interface. Além disso, ajudará você a entender o comportamento diferente, dependendo de qual etapa do ciclo de vida você decide atualizar sua IU.

Sinta-se à vontade para clonar e executar o seguinte repositório: https://github.com/wjosset/ReactToOrientation

Wilfried Josset
fonte
como fazer isso antes do iOS 13?
Daniel Springer
1
@DanielSpringer obrigado pelo seu comentário, eu editei o post para suportar iOS 12 e abaixo
Wilfried Josset
1
Acho esta resposta a melhor e mais informativa. Mas testar no iPadOS13.5 o uso do sugerido func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)não funcionou. Eu fiz funcionar usando func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator).
Andrej
12

Swift 4+: Eu estava usando isso para design de teclado virtual e, por algum motivo, o UIDevice.current.orientation.isLandscapemétodo continuava me dizendo que era Portrait, então aqui está o que usei:

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

    if(size.width > self.view.frame.size.width){
        //Landscape
    }
    else{
        //Portrait
    }
}
Asetniop
fonte
bem, eu tenho um problema semelhante no meu aplicativo iMessage. Não é muito estranho? E até a sua solução funciona ao contrário !! ¯_ (ツ) _ / ¯
Shyam
Não use UIScreen.main.bounds, porque ele pode fornecer os limites da tela antes da transição (preste atenção ao 'Will' no método), especialmente em múltiplas rotações rápidas. Você deve usar o parâmetro 'tamanho' ...
Dan Bodnar
@ dan-bodnar Você quer dizer o parâmetro nativeBounds?
asetniop
3
@asetniop, não. O sizeparâmetro que é injetado em viewWillTransitionToSize: withCoordinator: método que você escreveu acima. Isso refletirá o tamanho exato que sua visualização terá após a transição.
Dan Bodnar,
Esta não é uma boa solução se você estiver usando controladores de visualização filho, o tamanho do contêiner pode ser sempre quadrado, independentemente da orientação.
bojan
8

Swift 4.2, RxSwift

Se precisarmos recarregar collectionView.

NotificationCenter.default.rx.notification(UIDevice.orientationDidChangeNotification)
    .observeOn(MainScheduler.instance)
    .map { _ in }            
    .bind(to: collectionView.rx.reloadData)
    .disposed(by: bag)

Swift 4, RxSwift

Se precisarmos recarregar collectionView.

NotificationCenter.default.rx.notification(NSNotification.Name.UIDeviceOrientationDidChange)
    .observeOn(MainScheduler.instance)
    .map { _ in }            
    .bind(to: collectionView.rx.reloadData)
    .disposed(by: bag)
maslovsa
fonte
5

Se você estiver usando a versão Swift> = 3.0, existem algumas atualizações de código que você deve aplicar como outros já disseram. Só não se esqueça de ligar para super:

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

   super.viewWillTransition(to: size, with: coordinator)

   // YOUR CODE OR FUNCTIONS CALL HERE

}

Se você está pensando em usar um StackView para suas imagens, saiba que pode fazer algo como o seguinte:

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

   super.viewWillTransition(to: size, with: coordinator)

   if UIDevice.current.orientation.isLandscape {

      stackView.axis = .horizontal

   } else {

      stackView.axis = .vertical

   } // else

}

Se você estiver usando o Interface Builder, não se esqueça de selecionar a classe personalizada para este objeto UIStackView, na seção Identity Inspector no painel direito. Em seguida, basta criar (também por meio do Interface Builder) a referência IBOutlet para a instância UIStackView personalizada:

@IBOutlet weak var stackView: MyStackView!

Pegue a ideia e adapte-a às suas necessidades. Espero que isso possa ajudá-lo!

WiseRatel
fonte
Bom ponto sobre chamar super. É realmente uma codificação desleixada não chamar o método super em uma função substituída e pode produzir um comportamento imprevisível em aplicativos. E, potencialmente, bugs que são realmente difíceis de rastrear!
Adam Freeman
Você sabe por que não consigo substituir viewWillTransition ao criar uma subclasse de UIView?
Xcoder de
@Xcoder O motivo (geralmente) de um objeto não aplicar uma substituição está no fato de a instância de tempo de execução não ter sido criada com a classe personalizada, mas com o padrão. Se você estiver usando o Interface Builder, certifique-se de selecionar a classe personalizada para este objeto UIStackView, na seção Identity Inspector no painel direito.
WiseRatel 01 de
5

Swift 4

Tive alguns problemas menores ao atualizar a visualização ViewControllers usando UIDevice.current.orientation, como a atualização de restrições de células tableview durante a rotação ou animação de subvisualizações.

Em vez dos métodos acima, estou atualmente comparando o tamanho da transição ao tamanho da visualização dos controladores de visualização. Este parece ser o caminho certo a seguir, já que temos acesso a ambos neste ponto do código:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    print("Will Transition to size \(size) from super view size \(self.view.frame.size)")

    if (size.width > self.view.frame.size.width) {
        print("Landscape")
    } else {
        print("Portrait")
    }

    if (size.width != self.view.frame.size.width) {
        // Reload TableView to update cell's constraints.
    // Ensuring no dequeued cells have old constraints.
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }


}

Saída em um iPhone 6:

Will Transition to size (667.0, 375.0) from super view size (375.0, 667.0) 
Will Transition to size (375.0, 667.0) from super view size (667.0, 375.0)
RLoniello
fonte
Devo ter que ativar o suporte de orientação no nível do projeto?
Ericpoon
4

Acredito que a resposta correta seja, na verdade, uma combinação das duas abordagens: viewWIllTransition(toSize:)e NotificationCenter's UIDeviceOrientationDidChange.

viewWillTransition(toSize:)notifica você antes da transição.

NotificationCenter UIDeviceOrientationDidChangenotifica você depois .

Você tem que ser muito cuidadoso. Por exemplo, em UISplitViewControllerquando da rotação do dispositivo em determinadas linhas de orientação, a DetailViewControllerfica retirado da UISplitViewController's viewcontrollersmatriz, e empurrado para o mestre de UINavigationController. Se você procurar o controlador de visualização de detalhes antes de terminar a rotação, ele pode não existir e travar.

MH175
fonte
3

Todas as contribuições anteriores estão bem, mas uma pequena observação:

a) se a orientação for definida no plist, apenas retrato ou exemplo, você será não notificado por viewWillTransition

b) se precisarmos saber se o usuário girou o dispositivo (por exemplo, um jogo ou similar), só podemos usar:

NotificationCenter.default.addObserver(self, selector: #selector(ViewController.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)

testado em Xcode8, iOS11

ingconti
fonte
1

Para obter a orientação correta ao iniciar o aplicativo, você deve fazer o check-in viewDidLayoutSubviews(). Outros métodos descritos aqui não funcionarão.

Aqui está um exemplo de como fazer isso:

var mFirstStart = true

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if (mFirstStart) {
        mFirstStart = false
        detectOrientation()
    }
}

func detectOrientation() {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
        // do your stuff here for landscape
    } else {
        print("Portrait")
        // do your stuff here for portrait
    }
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    detectOrientation()
}

Isso funcionará sempre, na primeira inicialização do aplicativo e se girar enquanto o aplicativo está em execução .

lenooh
fonte
1

Você pode usar viewWillTransition(to:with:)e tocar em animate(alongsideTransition:completion:)para obter a orientação da interface APÓS a transição ser concluída. Você apenas precisa definir e implementar um protocolo semelhante a este para acessar o evento. Observe que este código foi usado para um jogo SpriteKit e sua implementação específica pode ser diferente.

protocol CanReceiveTransitionEvents {
    func viewWillTransition(to size: CGSize)
    func interfaceOrientationChanged(to orientation: UIInterfaceOrientation)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)

        guard
            let skView = self.view as? SKView,
            let canReceiveRotationEvents = skView.scene as? CanReceiveTransitionEvents else { return }

        coordinator.animate(alongsideTransition: nil) { _ in
            if let interfaceOrientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation {
                canReceiveRotationEvents.interfaceOrientationChanged(to: interfaceOrientation)
            }
        }

        canReceiveRotationEvents.viewWillTransition(to: size)
    }

Você pode definir pontos de interrupção nessas funções e observar que interfaceOrientationChanged(to orientation: UIInterfaceOrientation)sempre é chamado depois viewWillTransition(to size: CGSize)com a orientação atualizada.

The Rossinator
fonte
0

Outra forma de detectar as orientações do dispositivo é com a função traitCollectionDidChange (_ :). O sistema chama esse método quando o ambiente de interface do iOS muda.

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
{
    super.traitCollectionDidChange(previousTraitCollection)
    //...
}

Além disso, você pode usar a função willTransition (to: with :) (que é chamada antes de traitCollectionDidChange (_ :)), para obter informações antes de a orientação ser aplicada.

 override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)
{
    super.willTransition(to: newCollection, with: coordinator)
    //...
}
ckc
fonte