iOS 6: Como faço para restringir algumas visualizações ao retrato e permitir que outras girem?

99

Tenho um aplicativo para iPhone que usa um UINavigationControllerpara apresentar uma interface detalhada: primeiro uma visualização, depois outra, até quatro níveis de profundidade. Quero as três primeiras visualizações restritas à orientação retrato e somente a última visualização deve ser permitida a girar para paisagem. Ao retornar da quarta visualização para a terceira e a quarta visualização estava na orientação paisagem, quero que tudo gire de volta para o retrato.

No iOS 5, eu simplesmente defini shouldAutorotateToInterfaceOrientation:em cada um dos meus controladores de visualização para retornar SIM para as orientações permitidas. Tudo funcionou conforme descrito acima, incluindo o retorno ao retrato, mesmo se o dispositivo estivesse sendo mantido na orientação paisagem ao retornar do controlador de visualização nº 4 para o nº 3.

No iOS 6, todos os controladores de visualização giram para paisagem, interrompendo aqueles que não foram feitos para isso. As notas de lançamento do iOS 6 dizem

Mais responsabilidade é passar para o aplicativo e o delegado do aplicativo. Agora, os contêineres iOS (como UINavigationController) não consultam seus filhos para determinar se eles devem girar automaticamente. [...] O sistema pergunta ao controlador de exibição de tela inteira superior (normalmente o controlador de exibição raiz) para as orientações de interface suportadas sempre que o dispositivo gira ou sempre que um controlador de exibição é apresentado com o estilo de apresentação modal de tela inteira. Além disso, as orientações suportadas são recuperadas apenas se este controlador de visualização retornar YES de seu shouldAutorotatemétodo. [...] O sistema determina se uma orientação é suportada pela intersecção do valor retornado pelo supportedInterfaceOrientationsForWindow:método do aplicativo com o valor retornado pelo supportedInterfaceOrientationsmétodo do controlador de tela inteira superior.

Então eu criei uma subclasse UINavigationController, dei minha MainNavigationControllerpropriedade booleana landscapeOKe usei isso para retornar as orientações permitidas em supportedInterfaceOrientations. Então, em cada um dos viewWillAppear:métodos dos meus controladores de visualização, eu tenho uma linha como esta

    [(MainNavigationController*)[self navigationController] setLandscapeOK:YES];

para dizer ao meu MainNavigationControllero comportamento desejado.

Aí vem a pergunta: se eu agora navegar para a minha quarta visualização no modo retrato e virar o telefone, ele gira para a paisagem. Agora, pressiono o botão Voltar para retornar à minha terceira visualização, que deve funcionar apenas como retrato. Mas não gira de volta. Como faço isso?

eu tentei

    [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait]

no viewWillAppearmétodo do meu terceiro controlador de visualização, mas não faz nada. Este é o método errado para chamar ou talvez o lugar errado para chamá-lo ou devo implementar tudo de uma maneira totalmente diferente?

Harald Bögeholz
fonte

Respostas:

95

Eu tive o mesmo problema e encontrei uma solução que funciona para mim. Para torná-lo trabalho, é não suficiente para implementar - (NSUInteger)supportedInterfaceOrientationsem sua UINavigationController. Você também precisa implementar este método em seu controlador # 3, que é o primeiro a ser somente retrato após abrir o controlador # 4. Portanto, tenho o seguinte código em meu UINavigationController:

- (BOOL)shouldAutorotate
{
    return YES;
}

- (NSUInteger)supportedInterfaceOrientations
{
    if (self.isLandscapeOK) {
        // for iPhone, you could also return UIInterfaceOrientationMaskAllButUpsideDown
        return UIInterfaceOrientationMaskAll;
    }
    return UIInterfaceOrientationMaskPortrait;
}

Na visualização do controlador nº 3, adicione o seguinte:

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

Você não precisa adicionar nada aos controladores de visualização nº 1, nº 2 e nº 4. Isso funciona para mim, espero que ajude você.

Flo
fonte
8
você resolveu algo que as pessoas estavam tentando resolver há meses! você merece muito crédito por isso! Eu adicionei supportedInterfaceOrientations a uma categoria no UIViewController e ele funciona perfeitamente como um padrão para os controladores somente retrato.
dwery
3
Entre 100 respostas diferentes sobre SO, esta é a que realmente funciona. Obrigado :-)
Christer Nordvik
16
Eu tenho essa configuração e ainda obtendo a orientação errada ao voltar de retrato para um controlador de visualização restrito apenas a paisagem. Ele não gira automaticamente em relação à paisagem - mais alguém ainda está vendo?
Oded Ben Dov
1
e se o controlador de visualização de paisagem desejado estiver sendo apresentado a partir de um segue e não houver controlador de navegação?
jesses.co.tt
1
Edite o último para ser NSUInteger como tipo de retorno.
absessivo de
68

Adicionar um CustomNavigationController

Substitua esses métodos nele:

-(BOOL)shouldAutorotate
{
    return [[self.viewControllers lastObject] shouldAutorotate];
}

-(NSUInteger)supportedInterfaceOrientations
{
    return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
    return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
}

Agora adicione todas as orientações no plist

insira a descrição da imagem aqui

No controlador de visualização, adicione apenas os necessários:

-(BOOL)shouldAutorotate
{
    return YES;
}

-(NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

esses métodos substituem os métodos do controlador de navegação

Zaraki
fonte
5
Você realmente acertou em cheio com esta resposta! Muito obrigado. Você realmente ajudou muito. Isso tem sido um grande problema para mim por um tempo. Bom trabalho.
K2Digital
Obrigado! Isso me levou muito tempo e energia para descobrir!
bkbeachlabs
Você é o salva-vidas ... Isso também funciona no meu aplicativo. Na verdade, o que eu quero no meu aplicativo é que todos ViewControllerestejam no modo Retrato e o meu ViewControlleresteja no modo Paisagem e Retrato. Eu também dou suporte no iOS 5.0 e iOS 6.0. É por isso que estou confuso para contornar, mas esta é uma ótima solução. adiciono CustomNavigationController em meu controlador de exibição de raiz e dou suporte de acordo com minhas necessidades. Obrigado mais uma vez.
Bhavin_m
Fantástico. Exatamente o que eu precisava. Obrigado.
mszaro
e se o controlador de visualização de paisagem desejado estiver sendo apresentado a partir de um segue e não houver controlador de navegação?
jesses.co.tt
9

Depois de examinar todas as respostas em inúmeras perguntas semelhantes no SO, nenhuma das respostas funcionou para mim, mas eles me deram algumas ideias. Veja como acabei resolvendo o problema:

Primeiro, certifique-se de que as Orientações de interface suportadas no destino do projeto contenham todas as orientações que você deseja para a vista rotativa.

insira a descrição da imagem aqui

Em seguida, crie uma categoria de UINavigationController(já que a Apple diz para não criar uma subclasse):

@implementation UINavigationController (iOS6AutorotationFix)

-(BOOL)shouldAutorotate {
    return [self.topViewController shouldAutorotate];
}

@end

Importe essa categoria e o controlador de visualização que você deseja que seja capaz de girar (que chamarei RotatingViewController) para seu controlador de visualização de nível mais alto, que deve conter seu controlador de navegação. Nesse controlador de visualização, implemente da shouldAutorotateseguinte forma. Observe que este não deve ser o mesmo controlador de visualização que você deseja girar.

-(BOOL)shouldAutorotate {

    BOOL shouldRotate = NO;

    if ([navigationController.topViewController isMemberOfClass:[RotatingViewController class]] ) {
        shouldRotate = [navigationController.topViewController shouldAutorotate];
    }

    return shouldRotate;
}

Finalmente, em seu RotatingViewController, implemente shouldAutorotatee da supportedInterfaceOrientationsseguinte forma:

-(BOOL)shouldAutorotate {
    // Preparations to rotate view go here
    return YES;
}

-(NSUInteger)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskAllButUpsideDown; // or however you want to rotate
}

O motivo pelo qual você precisa fazer isso é porque o iOS 6 dá o controle de rotação ao controlador de visualização raiz em vez do controlador de visualização superior. Se você deseja que a rotação de uma visão individual se comporte de maneira diferente de outras visões na pilha, você precisa escrever um caso específico para ela no controlador de visão raiz.

Brian
fonte
2
Esta é uma excelente resposta que merece mais amor. Aqui está a informação que ele contém que não consegui encontrar em nenhum outro lugar, e que é crítica: se você quiser que a rotação de uma visualização individual se comporte de maneira diferente de outras visualizações na pilha, você precisa escrever um caso específico para ela na raiz visualizar controlador
shmim de
Suponho que isso funcionará se você tiver um navigationController como o primeiro membro do storyboard do seu aplicativo como controlador inicial, mas e o caso em que o controlador inicial é simplesmente um ViewController?
Pato
4

Não tenho reputação suficiente para comentar a resposta de @Brian, então acrescentarei minha observação aqui.

Brian mencionou que o iOS6 fornece o controle de rotação para o rootViewController - ele pode não ser apenas um UINavigationController como mencionado, mas também um UITabBarController, que era para mim. Minha estrutura é assim:

  • UITabBarController
    • UINavigationController
      • UIViewControllers ...
    • UINavigationController
      • UIViewControllers ...

Então, adicionei os métodos primeiro em um UITabBarController personalizado, depois em um UINavigationController personalizado e, por último, no UIViewController específico.

Exemplo de UITabBarController e UINavigationController:

- (BOOL)shouldAutorotate {
    return [self.viewControllers.lastObject shouldAutorotate];
}

- (NSUInteger)supportedInterfaceOrientations {
    return [self.viewControllers.lastObject supportedInterfaceOrientations];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    return [self.viewControllers.lastObject shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return [self.viewControllers.lastObject preferredInterfaceOrientationForPresentation];
}
micmdk
fonte
Na verdade, eu usei um UITabBarController como uma variável de instância em meu controlador de exibição de raiz porque você não deveria criar uma subclasse de UITabBarController nas versões do iOS anteriores a 6.0 e eu precisava dar suporte ao iOS 5.0.
Brian de
@micmdk Eu tenho o mesmo problema. por favor, informe como resolver, eu copiei seu código e coloquei no controlador de navegação customizado e customUabbarController e configurei outro controlador de visualização como Guiado de Brian. mas ainda não obtive controlador de visualização fixa em retrato.
Ram S
@Brian eu tenho o mesmo problema. por favor, informe como resolver, eu copiei seu código e coloquei no controlador de navegação customizado e customUabbarController e configurei outro controlador de visualização como Guiado de Micmdk. mas ainda não obtive controlador de visualização fixa em retrato no ios8.
Ram S
3

Eu gostaria de dar uma resposta parcial à minha própria pergunta. Achei a seguinte linha de código, usada no viewWillAppearmétodo do meu terceiro UIViewController, para funcionar:

[[UIDevice currentDevice] 
      performSelector:NSSelectorFromString(@"setOrientation:") 
           withObject:(id)UIInterfaceOrientationPortrait];

Mas eu realmente não gosto dessa solução. Ele está usando um truque para atribuir a uma propriedade somente leitura que, de acordo com a documentação da Apple, representa a orientação física do dispositivo. É como dizer ao iPhone para pular para a orientação correta na mão do usuário.

Estou muito tentado a deixar isso em meu aplicativo, pois simplesmente funciona. Mas não parece certo, então gostaria de deixar a questão em aberto para uma solução limpa.

Harald Bögeholz
fonte
4
Seu aplicativo provavelmente será rejeitado se você deixá-lo lá. Estou procurando uma solução para isso também, mas parece que não é possível. Tudo o que consegui encontrar foi uma resposta para esta pergunta: stackoverflow.com/questions/7196300/… isso meio que funciona, mas por algum motivo quebra o manuseio de toque de botão em um dos meus controladores de visualização
Lope,
Eu me arrisquei e me submeti à Apple. Ainda aguardando revisão ... Vou mantê-lo informado.
Harald Bögeholz,
A Apple acaba de aprovar meu aplicativo com este hack no lugar, yey! Espero que o tiro não saia pela culatra em uma atualização futura do iOS. Nesse ínterim, ainda estou aberto a sugestões para uma solução limpa!
Harald Bögeholz
1
@ Sugestão HaraldBögeholz Grt. mas quando estou trabalhando em novo xCode com ARC do que não posso usar o ans. Estou recebendo "O elenco de NSInteger (também conhecido como int) para id não é permitido com ARC PerformSelector pode causar um vazamento porque seu seletor é desconhecido" como posso resolver isso? Estou muito agradecido se tivermos solução. Quando desligo o ARC para visualização perticular, o aplicativo falha devido a algum vazamento de dados. Por favor, me sugira
Hitarth
4
Usar uma API privada NUNCA é a solução.
Javier Soto
2

Estou usando isso como suporte de orientação no ios 6.0

-(BOOL)shouldAutorotate{
    return YES;
}  

 - (NSUInteger)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskAll;
}


- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{  
  return UIInterfaceOrientationPortrait;
}
ASHISHT
fonte
1

Sendo que este é um tópico muito visto. Pensei em acrescentar o que acredito ser a resposta mais fácil. Isso funciona para ios8 e superior

-(BOOL)shouldAutorotate
{
    return YES;
}

e

-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}

É isso aí. Aproveitar!

Ah, e meus ViewControllers estão embutidos em um controlador de navegação, que eu não precisei criar uma subclasse ou configurar de forma alguma.

Reeder32
fonte
0

Isso pode não funcionar para todos, mas funciona muito bem para mim. Em vez de implementar ...

[(MainNavigationController*)[self navigationController] setLandscapeOK:YES];

em viewWillAppear em todos os meus controladores, decidi centralizar esse processo dentro da minha subclasse UINavigationController substituindo o método UINavigationControllerDelegatenavigationController:willShowViewController:animated:

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {

    self.previousVCLandscapeOK = self.isLandscapeOK; // Store the current VC's orientation preference before pushing on the new VC so we can set this again from within the custom "back" method
    self.isLandscapeOK = NO; // Set NO as default for all VC's
    if ([viewController isKindOfClass:[YourViewController class]]) {
        self.isLandscapeOK = YES;
    }
}

Descobri que esse método delegado não é chamado ao retirar um VC da pilha de navegação. Isso não foi um problema para mim, porque estou lidando com a funcionalidade de retorno de dentro da minha subclasse UINavigationController para que eu possa definir os botões da barra de navegação adequados e ações para VCs específicos como este ...

if ([viewController isKindOfClass:[ShareViewController class]]) {

    UIButton* backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 57, 30)];
    [backButton setImage:[UIImage imageNamed:@"back-arrow"] forState:UIControlStateNormal];
    [backButton addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem* backButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    viewController.navigationItem.leftBarButtonItem = backButtonItem;

    UIImageView* shareTitle = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"share-title"]];
    [shareTitle setContentMode:UIViewContentModeScaleAspectFit];
    [shareTitle setFrame:CGRectMake(0, 0, shareTitle.frame.size.width - 10, shareTitle.frame.size.height - 10)];
    viewController.navigationItem.titleView = shareTitle;

} else if(...) {
    ...
}

Aqui está como o meu backmétodo se parece para lidar com a retirada do VC da pilha e definir a preferência de rotação apropriada ...

- (void)back {
    self.isLandscapeOK = self.previousVCLandscapeOK;
    self.previousVCLandscapeOK = NO;
    [self popViewControllerAnimated:YES];
}

Então, como você pode ver, basicamente tudo o que está acontecendo é que primeiro estou definindo duas propriedades ...

@property (nonatomic) BOOL isLandscapeOK;
@property (nonatomic) BOOL previousVCLandscapeOK;

no navigationController:willShowViewController:animated:qual irá determinar quais são as orientações suportadas dentro daquele VC que está para ser apresentado. Ao abrir um VC, meu método "back" personalizado está sendo chamado e, em seguida, estou configurando isLandscapeOK para o que foi armazenado por meio do valor previousVCLandscapeOK.

Como eu disse, isso pode não funcionar para todos, mas funciona muito bem para mim e não tenho que me preocupar em adicionar código a cada um dos meus controladores de visualização, consegui manter tudo centralizado na subclasse UINavigationController.

Espero que isso ajude alguém como me ajudou. Obrigado, Jeremy.

Jeremy Fox
fonte
0

Vá para o arquivo Info.plist e faça a alteraçãoinsira a descrição da imagem aqui

Nekelle Latoya Celestine
fonte
0

Você deseja forçar o retrato do aplicativo iOS 6 somente então você pode adicionar a uma subclasse UIViewController métodos abaixo

- (BOOL)shouldAutorotate {
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        return YES;
    } else {
        return NO;
    }
}


- (NSUInteger)supportedInterfaceOrientations {
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        return UIInterfaceOrientationMaskAll;
    } else {
        return UIInterfaceOrientationMaskPortrait;
    }
}
Vaibhav Saran
fonte
2
O problema é que, a menos que essa visão seja a visão raiz (o que não é nesta questão), oAutorotate não deveria ser chamado.
Brian,
por qualquer motivo, return type of NSUIntegerlança um aviso, apesar de ser o tipo var correto. se você tiver TOC, use UIInterfaceOrientationMask.
Chris J de
Além disso, essa solução funcionou perfeitamente para um controlador de visualização modal (uma visualização de câmera) que eu possuo, sem qualquer necessidade de subclassificar UINavigationController. Estou supondo que os modais são tratados de forma independente. Não é relevante para a pergunta original, mas é uma informação útil para se ter um design inteligente.
Chris J de
0

Eu queria ter todos os meus VCs bloqueados na orientação retrato, exceto um. Isto é o que funcionou para mim.

  1. Adicione suporte para todas as orientações no arquivo plist.
  2. No controlador de visualização raiz, detecte o tipo de controlador de visualização que está na parte superior da janela e defina a orientação do aplicativo de acordo com o método supportedInterfaceOrientations . Por exemplo, eu precisava que meu aplicativo girasse apenas quando o webview estivesse no topo da pilha. Aqui está o que adicionei em meu rootVC:

    -(NSUInteger)supportedInterfaceOrientations
    {
        UIViewController *topMostViewController = [[Utils getAppDelegate] appNavigationController].topViewController;
        if ([topMostViewController isKindOfClass:[SVWebViewController class]]) {
            return UIInterfaceOrientationMaskAllButUpsideDown;
        }
        return UIInterfaceOrientationMaskPortrait;
    }
Trunal Bhanse
fonte
Trunal, isso tem alguns meses, mas você pode dar um pouco mais de detalhes sobre como detectar o tipo de controlador de visualização que está no topo. Especificamente, não tenho certeza do que você tem aqui [[Utils getAppDelegate] appNavigationController]. Suponho que utils seja alguma aula que você tenha, mas não sei o que você faz nela. Qualquer ajuda seria ótimo!
Ben
0

Não tenho reputação suficiente para responder à pergunta de @Ram S em @micmdk reply, portanto, adicionarei minha observação aqui.

Quando você usa UITabbarController , tente alterar self.viewControllers.lastObject no código de @ micmdk para self.selectedViewController assim:

- (BOOL)shouldAutorotate {
    return [self.selectedViewController shouldAutorotate];
}

- (NSUInteger)supportedInterfaceOrientations {
    return [self.selectedViewController supportedInterfaceOrientations];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    return [self.selectedViewController shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}
phantom_2
fonte
0

Você pode implementar e substituir shouldAutorotate () e supportedInterfaceOrientations var em todas as classes do View Controller que devem ser apresentadas em orientações diferentes daquelas definidas na PLIST do seu aplicativo.

No entanto, em uma interface de usuário não trivial, você pode enfrentar um problema para adicioná-la em dezenas de classes e não quer torná-las subclasses de várias que a suportam (MyBaseTableViewController, MyBaseNavigationController e MyBaseTabBarController).

Uma vez que você não pode sobrescrever esses métodos / var em UIViewController diretamente, você pode fazer isso em suas subclasses que são normalmente classes básicas suas, como UITableViewController, UINavigationController e UITabBarController.

Portanto, você pode implementar algumas extensões e ainda configurar MyPreciousViewController para mostrar em orientações diferentes de todos os outros, como este snippet de código do Swift 4:

extension UITableViewController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

    if let last = self.navigationController?.childViewControllers.last,
        last != self {
            return last.supportedInterfaceOrientations
    } else {
        return [.portrait]
    }
    }
}

extension MyPreciousViewController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

    return [.portrait,.landscape]
    }
}


extension UINavigationController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

        return [.portrait]
    }
}


extension UITabBarController {
    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

        return [.portrait]
    }
}
vedrano
fonte
0

Eu resolvi o mesmo tipo de problema.

Se você estiver usando UINavigationControllerpara enviar os controladores de visualização, você deve definir os métodos abaixo.

extension UINavigationController{

    override open var shouldAutorotate: Bool {

        if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
        {
            return true
        }
        return false
    }

    override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {

        if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
        {
            return .portrait
        }
        return .landscapeRight

    }
    override open var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {

        if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
        {
            return .portrait
        }
        return .landscapeRight
    }
}

No local de LoginViewControlleruso que UIViewControllervocê deseja mostrar. No meu caso, quero mostrar LoginViewControllerno Portraitmodo outro ViewControllersno landscapemodo.

Ramakrishna
fonte
-1

Use o seguinte método para resolver este problema

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

retorne apenas a orientação que desejar !!!

maxfiresolutions
fonte
3
Eu fiz isso, enquanto escrevia. Isso evita que meu controlador de visualização gire, mas não o força de volta para a única orientação suportada quando eu retorno a ele.
Harald Bögeholz
Isso não funciona para mim. Mesmo tendo incluído isso no meu controlador de visualização, a visualização ainda gira. O que da?
shim
por que downvote para maxfiresolutions? Funcionou para mim e o mesmo método dado por @Flo está tendo muitos votos positivos.
ViruMax