Como restringir a auto-rotação a uma orientação única para algumas vistas, enquanto permite todas as orientações sobre outras?

Esta questão é sobre rotação de dispositivos iOS e múltiplas visualizações controladas em um UINavigationController. Algumas visões devem ser restringidas à orientação de retrato , e algumas devem ser autorizadas livremente . Se você tentar criar a configuração mais simples com três visualizações, notará que o comportamento de autorotação apresenta algumas peculiaridades desagradáveis. O cenário é, no entanto, muito simples, então acho que não estou fazendo a implementação de auto-rotação corretamente, ou estou esquecendo alguma coisa.

Eu tenho um aplicativo de demonstração muito básico que mostra a estranheza e fiz um vídeo mostrando isso em ação.

  • Baixe o aplicativo (projeto XCode)
  • Veja as classs como uma essência (bastante longa)
  • Assista ao vídeo da pergunta (YouTube, 2m43s)

A configuração é muito básica: Três controladores de visualização chamados FirstViewController , SecondViewController e ThirdViewController estendem um AbstractViewController que mostra um label com o nome da class e que retorna YES para shouldAutorotateToInterfaceOrientation: quando o dispositivo está na orientação retrato. O SecondViewController substitui o método para permitir todas as rotações. Todas as três classs concretas adicionam alguns quadrados coloridos para poder navegar entre as visualizações, empurrando e estourando os controladores para dentro / fora do UINavigationController . Até agora, um cenário muito simples, eu diria.

Se você mantiver o dispositivo na orientação retrato ou paisagem, esse é o resultado que eu gostaria não apenas de alcançar, mas também de esperar. Na primeira imagem, você vê que todas as vistas estão ‘verticais’ e, no segundo, você vê que apenas o segundo controlador de visão gira a orientação do dispositivo. Para ser claro, deve ser possível navegar da segunda vista no modo paisagem para a terceira, mas como essa terceira só suporta a orientação retrato, ela deve ser mostrada apenas na orientação retrato. A maneira mais fácil de ver se os resultados são bons, é olhando para a posição da barra de transporte.

Orientação de vista esperada para o dispositivo no modo retratoOrientação de vista esperada para o dispositivo no modo paisagem

Mas esta questão está aqui porque o resultado real é completamente diferente. Dependendo de qual visualização você está quando você gira o dispositivo e, dependendo da visualização para a qual você navega, as exibições não serão didOrientFromInterfaceOrientation: (para ser específico, o método didOrientFromInterfaceOrientation: nunca é chamado). Se você estiver na paisagem no segundo e navegar para o terceiro, ele terá a mesma orientação que o segundo (= ruim). Se você navegar da segunda para a primeira, a canvas girará para um modo retrato “forçado” e a barra da portadora estará na parte superior física do dispositivo, independentemente de como você a segura. O vídeo mostra isso com mais detalhes.

Orientação real da vista para o dispositivo no modo paisagem

Minha pergunta é dupla:

  1. Por que o primeiro controlador de exibição gira de volta, mas não o terceiro?
  2. O que precisa ser feito para obter o comportamento correto de suas exibições quando você deseja que apenas algumas exibições sejam autoradas, mas não outras?

Felicidades, EP.

EDIT: Como último recurso antes de colocar uma recompensa por isso, eu reescrevi completamente esta questão para ser mais curto, mais claro e esperançosamente mais convidativo para dar uma resposta.

A resposta curta é que você está usando o UINavigationController, e isso não funcionará como você deseja. Dos documentos da Apple:

Por que meu UIViewController não roda com o dispositivo?

Todos os controladores de exibição filho em seu UITabBarController ou UINavigationController não concordam em um conjunto de orientação comum.

Para certificar-se de que todos os seus controllers de visão filho rodam corretamente, você deve implementar shouldAutorotateToInterfaceOrientation para cada view controller representando cada guia ou nível de navegação. Cada um deve concordar com a mesma orientação para que a rotação ocorra. Ou seja, todos devem retornar YES para as mesmas posições de orientação.

Você pode ler mais sobre os problemas de rotação de visualização aqui .

Você terá que rolar seu próprio gerenciamento de pilha de exibição / controlador para o que deseja fazer.

Faça um bolean no delegado do App para controlar qual orientação você quer, por exemplo, fazer um bool para ativar o Portrait e, no seu controlador de visualização, você deseja permitir que o Portrait o habilite pelo aplicativo compartilhado

no seu controlador de visualização, onde você deseja ativar ou desativar a orientação que quiser.

 ((APPNAMEAppDelegate *)[[UIApplication sharedApplication] delegate]).enablePortrait= NO; 

no Delegado de aplicativos.

 - (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window { NSLog(@"Interface orientations"); if(!enablePortrait) return UIInterfaceOrientationMaskLandscape; return UIInterfaceOrientationMaskLandscape|UIInterfaceOrientationMaskPortrait; } 

Esse método será acionado toda vez que você girar o dispositivo. Com base nesses BOOL, ative a orientação desejada.

Houve uma pergunta semelhante há alguns anos com várias respostas. Aqui está uma resposta recente de alguém para essa pergunta:
Existe uma maneira documentada de definir a orientação do iPhone?

Pelo que entendi, esse é um problema que muitas pessoas têm e, na maioria das vezes, hacks são a única maneira de consertar isso. Olhe através desse tópico se você não o viu antes e veja se alguma coisa funciona para você.

Em uma nota lateral, eu tive um problema semelhante há um tempo atrás quando eu estava chamando algo em shouldAutorotate e adicionei algum código para viewWillAppear para tentar consertá-lo. Eu sinceramente não me lembro se funcionou e eu não tenho mais um Mac para experimentá-lo, mas eu encontrei o código e vou colá-lo aqui, caso ele dê alguma inspiração.

 - (void)viewWillAppear:(BOOL)animated{ UIInterfaceOrientation o; switch ([UIDevice currentDevice].orientation) { case UIDeviceOrientationPortrait: o = UIInterfaceOrientationPortrait; break; case UIDeviceOrientationLandscapeLeft: o = UIInterfaceOrientationLandscapeLeft; break; case UIDeviceOrientationLandscapeRight: o = UIInterfaceOrientationLandscapeRight; break; default: break; } [self shouldAutorotateToInterfaceOrientation:o]; } 

NÃO UTILIZE ESTE HACK, APPLE REJEITA O APLICATIVO BASEADO NO USO DA ‘API PRIVADA’

Por uma questão de referência, deixarei minha resposta aqui, mas o uso da API privada não passará do quadro de revisão. Eu aprendi uma coisa hoje: D Como @younce citou os documentos da Apple corretamente, o que eu quero não pode ser alcançado com o UINavigationController.

Eu tinha duas opções. Primeiro, eu poderia ter escrito meu próprio substituto de controlador de navegação, com todos os horrores que alguém teria encontrado ao fazê-lo. Em segundo lugar, eu poderia ter hackeado a rotação nos view controllers, usando um recurso não documentado do UIDevice chamado setOrientation:animated:

Porque o segundo foi tentadoramente fácil, eu fui para aquele. Isso foi o que eu fiz. Você precisará de uma categoria para suprimir os avisos do compilador sobre o setter não existente:

 @interface UIDevice (UndocumentedFeatures) -(void)setOrientation:(UIInterfaceOrientation)orientation animated:(BOOL)animated; -(void)setOrientation:(UIInterfaceOrientation)orientation; @end 

Então você precisa verificar as orientações suportadas em viewWillAppear: Ao lado dos methods de UIDevice usados ​​aqui, você também pode forçar a orientação de retrato apresentando um controlador de exibição restrita, mas isso acontecerá instantaneamente e não será animado, portanto, essa é minha maneira preferida:

 -(void)viewWillAppear:(BOOL)animated { UIDevice *device = [UIDevice currentDevice]; UIDeviceOrientation realOrientation = device.orientation; if ([self shouldAutorotateToInterfaceOrientation:realOrientation]) { if (realOrientation != [UIApplication sharedApplication].statusBarOrientation) { // Resetting the orientation will trigger the application to rotate if ([device respondsToSelector:@selector(setOrientation:animated:)]) { [device setOrientation:realOrientation animated:animated]; } else { // Yes if Apple changes the implementation of this undocumented setter, // we're back to square one. } } } else if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationPortrait]) { if ([device respondsToSelector:@selector(setOrientation:animated:)]) { // Then set the desired orientation [device setOrientation:UIDeviceOrientationPortrait animated:animated]; // And set the real orientation back, we don't want to truly mess with the iPhone's balance system. // Because the view does not rotate on this orientation, it won't influence the app visually. [device setOrientation:realOrientation animated:animated]; } } } 

O truque é sempre manter a orientação do dispositivo interno para a orientação ‘real’ do dispositivo. Se você começar a alterar isso, a rotação do seu aplicativo ficará desbalanceada.

Mas, como sei agora, essa é apenas uma maneira certa de rejeitar seu aplicativo. Então, a opção número dois é apenas uma opção ruim. Reescreva esse NavigationController, ou apenas tenha todas as suas visualizações suportando o mesmo conjunto de orientação.

Felicidades, EP.

No iOS 6, isso se tornou um problema muito simples. Basta criar uma class especial para as visualizações que você deseja autorotizar. Então, no seu rootVC, adicione isso.

 -(BOOL)shouldAutorotate{ BOOL should = NO; NSLog(@"%@", [self.viewControllers[self.viewControllers.count-1] class]); if ([self.viewControllers[self.viewControllers.count-1] isKindOfClass:[YourAutorotatingClass class]]) { should = YES; } return should; } 

Eu sei que esta é uma pergunta antiga, mas achei que valeria a pena mencionar.