Qual é a maneira “certa” de lidar com as mudanças de orientação no iOS 8?

104

Alguém pode me dizer a abordagem "certa" ou "melhor" para trabalhar com orientações de interface em retrato e paisagem no iOS 8? Parece que todas as funções que desejo usar para esse fim estão obsoletas no iOS 8, e minha pesquisa não encontrou nenhuma alternativa clara e elegante. Devo realmente observar a largura e a altura para determinar por mim mesmo se estamos no modo retrato ou paisagem?

Por exemplo, no meu controlador de visualização, como devo implementar o seguinte pseudocódigo?

if we are rotating from portrait to landscape then
  do portrait things
else if we are rotating from landscape to portrait then
  do landscape things
rmp251
fonte
3
Leia a documentação para UIViewController. Veja a seção intitulada "Manipulando Rotações de Visualização". Ela explica o que você deve fazer.
rmaddy
Que eles estão obsoletos é uma pista. Você tem que usar outra coisa ... essa outra coisa deve ser AutoLayout e Classes de tamanho :-)
Aaron

Respostas:

262

A Apple recomenda o uso de classes de tamanho como uma medida aproximada de quanto espaço de tela está disponível, para que sua IU possa alterar significativamente seu layout e aparência. Considere que um iPad em retrato tem as mesmas classes de tamanho que em paisagem (largura regular, altura regular). Isso significa que sua IU deve ser mais ou menos semelhante entre as duas orientações.

No entanto, a mudança de retrato para paisagem em um iPad é significativa o suficiente para que você precise fazer alguns ajustes menores na IU, mesmo que as classes de tamanho não tenham mudado. Como os métodos relacionados à orientação da interface em UIViewControllerforam descontinuados, a Apple agora recomenda a implementação do novo método a seguir em UIViewControllercomo um substituto:

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
{
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    // Code here will execute before the rotation begins.
    // Equivalent to placing it in the deprecated method -[willRotateToInterfaceOrientation:duration:]

    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {

        // Place code here to perform animations during the rotation.
        // You can pass nil or leave this block empty if not necessary.

    } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {

        // Code here will execute after the rotation has finished.
        // Equivalent to placing it in the deprecated method -[didRotateFromInterfaceOrientation:]

    }];
}

Ótimo! Agora você está recebendo retornos de chamada antes do início da rotação e depois que termina. Mas que tal saber realmente se a rotação é para retrato ou para paisagem?

A Apple recomenda pensar na rotação simplesmente como uma mudança no tamanho da visão dos pais. Em outras palavras, durante a rotação do iPad de retrato para paisagem, você pode pensar nisso como a visualização no nível da raiz simplesmente mudando bounds.sizede {768, 1024}para {1024, 768}. Sabendo isto, em seguida, você deve usar o sizepassado para o viewWillTransitionToSize:withTransitionCoordinator:método acima para descobrir se você está girando para retrato ou paisagem.

Se você deseja uma maneira ainda mais perfeita de migrar o código legado para a nova forma de fazer as coisas do iOS 8, considere usar esta categoria simples no UIView, que pode ser usada para determinar se uma visualização é "retrato" ou "paisagem" com base em seu Tamanho.

Para recapitular:

  1. Você deve usar classes de tamanho para determinar quando mostrar interfaces de usuário fundamentalmente diferentes (por exemplo, uma IU "semelhante ao iPhone" vs. uma IU "semelhante ao iPad")
  2. Se você precisar fazer ajustes menores em sua IU quando as classes de tamanho não mudam, mas o tamanho do contêiner (visualização pai) sim, como quando um iPad gira, use o viewWillTransitionToSize:withTransitionCoordinator:retorno de chamada em UIViewController.
  3. Cada visualização em seu aplicativo deve apenas tomar decisões de layout com base no espaço que foi dado para o layout. Deixe a hierarquia natural de visualizações cascatear essas informações.
  4. Da mesma forma, não use o statusBarOrientation- que é basicamente uma propriedade no nível do dispositivo - para determinar se deve fazer o layout de uma visualização para "retrato" ou "paisagem". A orientação da barra de status só deve ser usada por código que lida com coisas como as UIWindowque realmente vivem no nível da raiz do aplicativo.
smileyborg
fonte
5
Excelente resposta! Aliás, seu vídeo do youtube em AL foi incrivelmente informativo. Obrigado por compartilhar. Confira! youtube.com/watch?v=taWaW2GzfCI
smileBot
2
O único problema que encontrei é que às vezes você quer saber a orientação do dispositivo e isso não permite que você saiba. No iPad, é chamado antes da barra de status ou do valor de orientação do dispositivo mudar. Você não pode dizer se o usuário segura o dispositivo em retrato ou retrato de cabeça para baixo. Também é impossível testar, pois zombar UIViewControllerTransitionCoordinatoré um pesadelo :-(
Darrarski
@Darrarski, você encontrou uma solução elegante para lidar com esses problemas?
Xvolks de
então onde está viewdidlayoutsubviews? Achei que viewdidlayoutsubviewsé usado para capturar mudanças de limite devido à rotação. Você pode elaborar sobre isso?
Mel,
1
Como diferenciar entre paisagem esquerda e paisagem direita se não usarmos a orientação da barra de status? Eu preciso dessa distinção. Além disso, há outros problemas descritos aqui - stackoverflow.com/questions/53364498/…
Deepak Sharma
17

Com base na resposta muito bem detalhada (e aceita) do smileyborg, aqui está uma adaptação usando o swift 3:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: nil, completion: {
        _ in
        self.collectionView.collectionViewLayout.invalidateLayout()
    })        
}

E na UICollectionViewDelegateFlowLayoutimplementação,

public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    // retrieve the updated bounds
    let itemWidth = collectionView.bounds.width
    let itemHeight = collectionView.bounds.height
    // do whatever you need to do to adapt to the new size
}
CMont
fonte
11

Eu simplesmente uso a Central de notificações:

Adicione uma variável de orientação (explicarei no final)

//Above viewdidload
var orientations:UIInterfaceOrientation = UIApplication.sharedApplication().statusBarOrientation

Adicionar notificação quando a visualização for exibida

override func viewDidAppear(animated: Bool) {
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "orientationChanged:", name: UIDeviceOrientationDidChangeNotification, object: nil)
}

Remover notificação quando a visualização for embora

override func viewWillDisappear(animated: Bool) {
        NSNotificationCenter.defaultCenter().removeObserver(self, name: UIDeviceOrientationDidChangeNotification, object: nil) 
}

Obtém a orientação atual quando a notificação é acionada

func orientationChanged (notification: NSNotification) {
    adjustViewsForOrientation(UIApplication.sharedApplication().statusBarOrientation)
}

Verifica a orientação (retrato / paisagem) e lida com eventos

func adjustViewsForOrientation(orientation: UIInterfaceOrientation) {
    if (orientation == UIInterfaceOrientation.Portrait || orientation == UIInterfaceOrientation.PortraitUpsideDown)
    {
        if(orientation != orientations) {
            println("Portrait")
            //Do Rotation stuff here
            orientations = orientation
        }
    }
    else if (orientation == UIInterfaceOrientation.LandscapeLeft || orientation == UIInterfaceOrientation.LandscapeRight)
    {
       if(orientation != orientations) {
            println("Landscape")
            //Do Rotation stuff here
            orientations = orientation
        }
    }
}

A razão pela qual adiciono uma variável de orientação é porque, ao testar em um dispositivo físico, a notificação de orientação é chamada a cada pequeno movimento no dispositivo, e não apenas quando ele gira. Adicionar as instruções var e if apenas chama o código se ele mudou para a orientação oposta.

inVINCEable
fonte
11
Esta não é a abordagem recomendada pela Apple porque significa que cada visualização em seu aplicativo está decidindo como exibir com base na orientação do dispositivo, em vez de considerar o espaço que lhe foi dado.
smileyborg de
2
A Apple também não está levando o hardware em consideração com o 8.0. Diga a uma camada AVPreview que ela não precisa se preocupar com a orientação se for apresentada sobre um controlador de visualização. Não funciona, mas está corrigido em 8.2
Nick Turner,
superde viewDidAppeare viewWillDisappeardeve ser chamado
Andrew Bogaevskyi
Nota: UIDeviceOrientation! = UIInterfaceOrientation. Em meus experimentos, statusBarOrientation não é confiável.
nnrales
2

Do ponto de vista da IU, acredito que o uso de classes de tamanho é a abordagem recomendada da Apple para lidar com interfaces em diferentes orientações, tamanhos e escalas.

Consulte a seção: Traços Descrevem a Classe de Tamanho e Escala de uma Interface aqui: https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS8.html

"O iOS 8 adiciona novos recursos que tornam o tratamento do tamanho da tela e da orientação muito mais versátil."

Este também é um bom artigo: https://carpeaqua.com/thinking-in-terms-of-ios-8-size-classes/

EDITAR Link atualizado: https://carpeaqua.com/2014/06/14/thinking-in-terms-of-ios-8-size-classes/ (Crédito: Koen)

Aaron
fonte
Sim, parece que todo o seu site está fora do ar no momento.
Aaron,
Vale a pena ler o artigo e aqui está o endereço correto: carpeaqua.com/thinking-in-terms-of-ios-8-size-classes
uem
Mas e se não estivermos falando sobre classes de tamanho e UI, mas sobre AVFoundation, por exemplo. Ao gravar vídeo, em alguns casos, você deve saber a orientação dos viewControllers para definir os metadados corretos. Isso é simplesmente impossível. "versátil".
Julian F. Weinert de
Não era sobre isso que o OP estava perguntando / falando.
Aaron de
1
Link atualizado: carpeaqua.com/2014/06/14/…
koen