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!


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

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

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

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


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

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)

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!


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)

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.


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


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 .


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.


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