Existe uma maneira documentada de definir a orientação do iPhone?

94

Tenho um aplicativo em que gostaria de oferecer suporte à rotação do dispositivo em certas visualizações, mas outras não fazem muito sentido no modo Paisagem, então, ao trocar as visualizações, gostaria de forçar a rotação a ser definida como retrato.

Há um setter de propriedade não documentado em UIDevice que faz o truque, mas obviamente gera um aviso do compilador e pode desaparecer com uma revisão futura do SDK.

[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];

Existem maneiras documentadas de forçar a orientação?

Atualização: pensei em fornecer um exemplo, pois não estou procurando shouldAutorotateToInterfaceOrientation, pois já o implementei.

Quero que meu aplicativo suporte paisagem e retrato na Visualização 1, mas apenas retrato na Visualização 2. Já implementei shouldAutorotateToInterfaceOrientation para todas as visualizações, mas se o usuário estiver no modo paisagem na Visualização 1 e, em seguida, alternar para Visualização 2, quero forçar o telefone para girar de volta para Retrato.

Dave Verwer
fonte
11
Eu registrei um bug sobre isso, sugerindo que a Apple exponha a API acima ou honre o SIM, NÃO sendo retornado do método shouldRotate, quando uma visualização é carregada pela primeira vez, não apenas quando o telefone gira.
rustyshelf
2
Registrei
1
[[UIDevice currentDevice] setOrientation: UIInterfaceOrientationPortrait]; Este método está obsoleto e não existe mais.
Hikmat Khan

Respostas:

6

Isso não é mais um problema no SDK do iPhone 3.1.2 posterior. Agora parece respeitar a orientação solicitada da visualização sendo empurrada de volta para a pilha. Isso provavelmente significa que você precisaria detectar versões mais antigas do iPhone OS e aplicar setOrientation apenas quando for anterior à versão mais recente.

Não está claro se a análise estática da Apple compreenderá que você está contornando as limitações do SDK mais antigo. A Apple me disse pessoalmente para remover a chamada do método na minha próxima atualização, então ainda não tenho certeza se um hack para dispositivos mais antigos passará pelo processo de aprovação.

John K
fonte
Não é o caso do iPhone Simulator 3.3.2. Ainda não está funcionando.
Pato
4
@user Como você solicita uma orientação quando empurra uma visualização na pilha?
programa de
4
Você pode contornar a análise estática da Apple. Eu uso com sucesso código não documentado usando performSelector e confiando no incrível dinamismo do Obj-C.
Kenneth Ballenegger
@KennethBallenegger Lembro-me de ter lido nas diretrizes e / ou acordo que ocultar coisas como essa pode fazer com que seu aplicativo seja removido e talvez até mesmo sua conta seja removida. Eu não arriscaria isso apenas para usar API não documentada.
Ivan Vučica
101

Isso é muito depois do fato, mas apenas no caso de aparecer alguém que não esteja usando um controlador de navegação e / ou não queira usar métodos não documentados:

UIViewController *c = [[UIViewController alloc]init];
[self presentModalViewController:c animated:NO];
[self dismissModalViewControllerAnimated:NO];
[c release];

É suficiente apresentar e descartar um controlador de visualização vanilla.

Obviamente, você ainda precisará confirmar ou negar a orientação em sua substituição de shouldAutorotateToInterfaceOrientation. Mas isso fará com que o shouldAutorotate ... seja chamado novamente pelo sistema.

Josh
fonte
1
Incrível, esta não está marcada como a resposta correta desta postagem, mas resolveu metade do meu problema stackoverflow.com/questions/5030315 . O truque da visualização do modelo 'força' a orientação do retrato. Você também conhece um para forçar a paisagem?
epologee de
5
Hah hah !! isso é muito estúpido! MAS FUNCIONA! - Claro que sim! : D
hfossli
Incrível, tenho lutado com isso há muito tempo. Obrigado! companheiro
Rahul Choudhary,
3
Isso parece funcionar para iOS5 e iOS6: [self presentViewController: [UIViewController novo] animado: SEM conclusão: ^ {[self CC0ViewControllerAnimated: SEM conclusão: nulo]; }];
Inferis
1
Isso parece funcionar no iOS 7 e iOS 8, mas há uma tela preta perceptível que pisca à vista (iOS 8 principalmente).
levigroker,
16

Se você quiser forçá-lo a girar de retrato para paisagem, aqui está o código. Apenas observe que você precisa ajustar o centro de sua visão. Percebi que o meu não colocava a vista no lugar certo. Caso contrário, funcionou perfeitamente. Obrigado pela dica.

if(UIInterfaceOrientationIsLandscape(self.interfaceOrientation)){

        [UIView beginAnimations:@"View Flip" context:nil];
        [UIView setAnimationDuration:0.5f];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

        self.view.transform = CGAffineTransformIdentity;
        self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
        self.view.bounds = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
        self.view.center = CGPointMake(160.0f, 240.0f);

        [UIView commitAnimations];
}
Michael Gaylord
fonte
Também funcionará se eu adicionar um UIAlertView? Tentei CGAffineTransformMakeRotationgirar a vista, mas o UIAlertView ainda está de acordo com a orientação da barra de status, ou seja, -degreeToRadian (ângulo)? Você conseguiu algo assim?
NeverHopeless
A maneira mais fácil de fazer isso com um UIAlertViewé definir seu controlador de visualização como o delegado do alertView e, em seguida, em:, - (void) didPresentAlertView:(UIAlertView *) alertViewvocê executa a rotação com base na corrente [UIApplication sharedApplication].statusBarOrientation. Se você não animar, não deve parecer muito estranho.
Michael Gaylord
16

Pelo que posso dizer, o setOrientation:método não funciona (ou talvez não funcione mais). Aqui está o que estou fazendo para fazer isso:

primeiro, coloque esta definição no topo do seu arquivo, logo abaixo do seu #imports:

#define degreesToRadian(x) (M_PI * (x) / 180.0)

então, no viewWillAppear:método

[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];     
if (self.interfaceOrientation == UIInterfaceOrientationPortrait) {  
    self.view.transform = CGAffineTransformIdentity;
    self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
    self.view.bounds = CGRectMake(0.0, 0.0, 480, 320);
}

se você quiser que seja animado, pode embrulhar tudo em um bloco de animação, assim:

[UIView beginAnimations:@"View Flip" context:nil];
[UIView setAnimationDuration:1.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];     
if (self.interfaceOrientation == UIInterfaceOrientationPortrait) {  
    self.view.transform = CGAffineTransformIdentity;
    self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
    self.view.bounds = CGRectMake(0.0, 0.0, 480, 320);
}
[UIView commitAnimations];

Então, em seu controlador de modo retrato, você pode fazer o inverso - verifique se ele está atualmente em paisagem e, se estiver, gire-o de volta para Retrato.

Bdebeez
fonte
1
Em vez de self.view, você deve girar self.window.view. Isso é útil se você tiver UITabBar ou outros controladores no topo de sua visualização.
Borut Tomazin
7

Eu estava tendo um problema em que tinha um UIViewControllerna tela, em um UINavigationController, na orientação paisagem. Quando o próximo controlador de visualização é colocado no fluxo, no entanto, eu preciso que o dispositivo volte para a orientação retrato.

O que percebi é que o shouldAutorotateToInterfaceOrientation:método não é chamado quando um novo controlador de visualização é colocado na pilha, mas é chamado quando um controlador de visualização é retirado da pilha .

Aproveitando isso, estou usando este snippet de código em um de meus aplicativos:

- (void)selectHostingAtIndex:(int)hostingIndex {

    self.transitioning = YES;

    UIViewController *garbageController = [[[UIViewController alloc] init] autorelease];
    [self.navigationController pushViewController:garbageController animated:NO];
    [self.navigationController popViewControllerAnimated:NO];

    BBHostingController *hostingController = [[BBHostingController alloc] init];
    hostingController.hosting = [self.hostings objectAtIndex:hostingIndex];
    [self.navigationController pushViewController:hostingController animated:YES];
    [hostingController release];

    self.transitioning = NO;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    if (self.transitioning)
        return (toInterfaceOrientation == UIInterfaceOrientationPortrait);
    else
        return YES;
}

Basicamente, criando um controlador de visualização vazio, colocando-o na pilha e imediatamente retirando-o, é possível fazer com que a interface volte para a posição retrato. Uma vez que o controlador foi acionado, eu simplesmente pressiono o controlador que pretendia inserir em primeiro lugar. Visualmente, parece ótimo - o controlador de visualização vazio e arbitrário nunca é visto pelo usuário.

Andrew Vilcsak
fonte
Olá, acabei de implementar seu método para forçar um VC na orientação correta antes de empurrá-lo, mas descobri que ele só funciona ao forçar orientações de retrato (se a visualização atual for retrato e você quiser forçar a próxima para paisagem, ela venceu chame shouldAutorotateToInterfaceOrientation: ao abrir). Você encontrou uma solução alternativa para forçar uma visualização para Paisagem?
Rafael Nobre
Interessante - não tive esse problema, desculpe. Eu tentaria brincar com a sequência de empurrões e estalos, há uma boa chance de você chegar a alguma coisa.
Andrew Vilcsak
7

Há uma maneira simples de forçar programaticamente o iPhone para a orientação necessária - usando duas das respostas já fornecidas por kdbdallas , Josh :

//will rotate status bar
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];


//will re-rotate view according to statusbar
UIViewController *c = [[UIViewController alloc]init];
[self presentModalViewController:c animated:NO];
[self dismissModalViewControllerAnimated:NO];
[c release];

Funciona como um encanto :)

EDITAR:

para iOS 6, preciso adicionar esta função: (funciona no viewcontroller modal)

- (NSUInteger)supportedInterfaceOrientations
{
    return (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight);
}
Guntis Treulands
fonte
Para mim não funciona muito bem: a barra de navegação e a barra de ferramentas encolhem para a altura que teriam na visualização Paisagem. O método de remover e adicionar novamente a vista superior mantém a altura correta, mas também tem uma desvantagem: minha barra de navegação se move um pouco para baixo (como se a barra de medição estivesse habilitada)
AlexWien
Funciona do meu lado, tanto no viewcontroller modal quanto no push. Mas apenas no iOS 5. :(
Guntis Treulands
Sim, também funcionei no iOS5. Agora, no iOS6, a altura da barra de navegação diminui quando o controlador de visualização modal da paisagem é dispensado
AlexWien
7

Tenho cavado e cavado procurando uma boa solução para isso. Encontrei esta postagem de blog que resolve o problema: remova sua visão externa da chave UIWindowe adicione-a novamente, o sistema irá então consultar novamente os shouldAutorotateToInterfaceOrientation:métodos de seus controladores de visão, reforçando a orientação correta a ser aplicada. Veja: iphone forçando uiview a reorientar

Rafael Nobre
fonte
Esta solução funcionou melhor para mim. É importante ressaltar que a correção que apresenta um modalViewController vazio causa animação incorreta de envio de viewcontrollers para a pilha. Este método UIWindow não sofre o mesmo problema.
Rik Smith-Unna
5

A resposta de Josh funciona bem para mim.

No entanto, prefiro postar uma notificação "a orientação mudou, atualize a IU" . Quando esta notificação é recebida por um controlador de visualização, ele chama shouldAutorotateToInterfaceOrientation:, permitindo que você defina qualquer orientação retornando YESpara a orientação desejada.

[[NSNotificationCenter defaultCenter] postNotificationName:UIDeviceOrientationDidChangeNotification object:nil];

O único problema é que isso força uma reorientação sem animação. Você precisaria quebrar essa linha entre beginAnimations:e commitAnimationspara obter uma transição suave.

Espero que ajude.

Christian Schnorr
fonte
3

FWIW, aqui está minha implementação de definir manualmente a orientação (para ir no controlador de visualização raiz do seu aplicativo, natch):

-(void)rotateInterfaceToOrientation:(UIDeviceOrientation)orientation{

    CGRect bounds = [[ UIScreen mainScreen ] bounds ];
    CGAffineTransform t;
    CGFloat r = 0;
    switch ( orientation ) {
        case UIDeviceOrientationLandscapeRight:
            r = -(M_PI / 2);
            break;
        case UIDeviceOrientationLandscapeLeft:
            r  = M_PI / 2;
            break;
    }
    if( r != 0 ){
        CGSize sz = bounds.size;
        bounds.size.width = sz.height;
        bounds.size.height = sz.width;
    }
    t = CGAffineTransformMakeRotation( r );

    UIApplication *application = [ UIApplication sharedApplication ];

    [ UIView beginAnimations:@"InterfaceOrientation" context: nil ];
    [ UIView setAnimationDuration: [ application statusBarOrientationAnimationDuration ] ];
    self.view.transform = t;
    self.view.bounds = bounds;
    [ UIView commitAnimations ];

    [ application setStatusBarOrientation: orientation animated: YES ];     
}

juntamente com o seguinte UINavigationControllerDelegatemétodo (supondo que você esteja usando um UINavigationController):

-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
    // rotate interface, if we need to
    UIDeviceOrientation orientation = [[ UIDevice currentDevice ] orientation ];
    BOOL bViewControllerDoesSupportCurrentOrientation = [ viewController shouldAutorotateToInterfaceOrientation: orientation ];
    if( !bViewControllerDoesSupportCurrentOrientation ){
        [ self rotateInterfaceToOrientation: UIDeviceOrientationPortrait ];
    }
}

Isso se encarrega de girar a vista raiz de acordo com se uma entrada UIViewControllersuporta a orientação atual do dispositivo. Finalmente, você vai querer se conectar rotateInterfaceToOrientationàs mudanças reais de orientação do dispositivo para imitar a funcionalidade padrão do iOS. Adicione este manipulador de eventos ao mesmo controlador de visualização raiz:

-(void)onUIDeviceOrientationDidChangeNotification:(NSNotification*)notification{
    UIViewController *tvc = self.rootNavigationController.topViewController;
    UIDeviceOrientation orientation = [[ UIDevice currentDevice ] orientation ];
    // only switch if we need to (seem to get multiple notifications on device)
    if( orientation != [[ UIApplication sharedApplication ] statusBarOrientation ] ){
        if( [ tvc shouldAutorotateToInterfaceOrientation: orientation ] ){
            [ self rotateInterfaceToOrientation: orientation ];
        }
    }
}

Por fim, registre-se para UIDeviceOrientationDidChangeNotificationreceber notificações em initou loadviewassim:

[[ NSNotificationCenter defaultCenter ] addObserver: self
                                           selector: @selector(onUIDeviceOrientationDidChangeNotification:)
                                               name: UIDeviceOrientationDidChangeNotification
                                             object: nil ];
[[ UIDevice currentDevice ] beginGeneratingDeviceOrientationNotifications ];
Henry Cooke
fonte
Esta é a única das respostas nesta página que consegui adaptar com sucesso ao código do meu controlador de navegação. O único problema menor é que a barra de navegação girada não muda a altura entre as orientações retrato e paisagem como faria se fosse girada pelo mecanismo normal.
Dan Dyer
@DanDyer, você resolveu o problema com a altura errada da barra de navegação? (Especialmente em iOS6, a altura está errada)
AlexWien
@AlexWien Finalmente consegui convencer o cliente de que não deveríamos fazer isso. Para iOS 6, pode ser necessário fazer algo com o novo shouldAutorotatemétodo (consulte stackoverflow.com/a/12586002/5171 ).
Dan Dyer
@DanDyer resolveu ontem, um simples ocultar e mostrar de navegação e barra de status em ViewDidAppear resolveu. Agora tudo funciona (SDK ios6, alvo ios5) Eu tenho que testar novamente com alvo ios6
AlexWien
2

Isso funciona para mim (obrigado Henry Cooke):

O objetivo para mim era lidar apenas com as mudanças de orientação da paisagem.

método init:

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(orientationChanged:)
                                             name:UIDeviceOrientationDidChangeNotification
                                           object:nil];

- (void)orientationChanged:(NSNotification *)notification {    
    //[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
    UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
    CGRect bounds = [[ UIScreen mainScreen ] bounds ];
    CGAffineTransform t;
    CGFloat r = 0;
    switch ( orientation ) {
        case UIDeviceOrientationLandscapeRight:
            r = 0;
            NSLog(@"Right");
            break;
        case UIDeviceOrientationLandscapeLeft:
            r  = M_PI;
            NSLog(@"Left");
            break;
        default:return;
    }

    t = CGAffineTransformMakeRotation( r );

    UIApplication *application = [ UIApplication sharedApplication ];
    [ UIView beginAnimations:@"InterfaceOrientation" context: nil ];
    [ UIView setAnimationDuration: [ application statusBarOrientationAnimationDuration ] ];
    self.view.transform = t;
    self.view.bounds = bounds;
    [ UIView commitAnimations ];

    [ application setStatusBarOrientation: orientation animated: YES ];
}
user324168
fonte
1

Tenho um aplicativo em que gostaria de oferecer suporte à rotação do dispositivo em certas visualizações, mas outras não fazem muito sentido no modo Paisagem, então, ao trocar as visualizações, gostaria de forçar a rotação a ser definida como retrato.

Percebo que a postagem original acima neste tópico é muito antiga agora, mas eu tive um problema semelhante a ela - isto é. todas as telas em meu aplicativo são apenas retrato, com exceção de uma tela, que pode ser girada entre paisagem e retrato pelo usuário.

Isso foi bastante direto, mas como em outras postagens, eu queria que o aplicativo retornasse automaticamente ao modo retrato, independentemente da orientação atual do dispositivo, ao retornar à tela anterior.

A solução que implementei foi ocultar a Barra de Navegação no modo paisagem, o que significa que o usuário só pode retornar às telas anteriores enquanto estiver no retrato. Portanto, todas as outras telas só podem estar em retrato.

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)pInterfaceOrientation {
    BOOL lHideNavBar = self.interfaceOrientation == UIInterfaceOrientationPortrait ? NO : YES;
    [self.navigationController setNavigationBarHidden:lHideNavBar animated:YES];
}

Isso também tem o benefício adicional para meu aplicativo, pois há mais espaço de tela disponível no modo paisagem. Isso é útil porque a tela em questão é usada para exibir arquivos PDF.

Espero que isto ajude.

campainha
fonte
1

Resolvi isso com bastante facilidade no final. Tentei todas as sugestões acima e ainda não consegui, então esta foi minha solução:

No ViewController que precisa permanecer como Paisagem (Esquerda ou Direita), eu escuto as mudanças de orientação:

    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(didRotate:)
                                             name:UIDeviceOrientationDidChangeNotification object:nil];

Então, em didRotate:

- (void) didRotate:(NSNotification *)notification
{   if (orientationa == UIDeviceOrientationPortrait) 
    {
        if (hasRotated == NO) 
        {
            NSLog(@"Rotating to portait");
            hasRotated = YES;
            [UIView beginAnimations: @"" context:nil];
            [UIView setAnimationDuration: 0];
            self.view.transform = CGAffineTransformIdentity;
            self.view.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-90));
            self.view.bounds = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
            self.view.frame = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
            [UIView commitAnimations];

    }
}
else if (UIDeviceOrientationIsLandscape( orientationa))
{
    if (hasRotated) 
    {
        NSLog(@"Rotating to lands");
        hasRotated = NO;
        [UIView beginAnimations: @"" context:nil];
        [UIView setAnimationDuration: 0];
        self.view.transform = CGAffineTransformIdentity;
        self.view.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(0));
        self.view.bounds = CGRectMake(0.0f, 0.0f, 320.0f, 480.0f);
        self.view.frame = CGRectMake(0.0f, 0.0f, 320.0f, 480.0f);
        [UIView commitAnimations];

    }
}

Lembre-se de quaisquer Super Views / Subviews que usam o redimensionamento automático, já que view.bounds / frame estão sendo redefinidos explicitamente ...

A única ressalva a este método para manter a vista Paisagem, é a animação inerente alternando entre as orientações que deve ocorrer, quando seria melhor que parecesse não ter alteração.

David van Dugteren
fonte
1

Solução iOS 6:

[[[self window] rootViewController] presentViewController:[[UIViewController alloc] init] animated:NO completion:^{
    [[[self window] rootViewController] dismissViewControllerAnimated:NO completion:nil];
}];

O código exato depende do aplicativo e também de onde você o coloca (eu usei no meu AppDelegate). Substitua [[self window] rootViewController]pelo que você usa. Eu estava usando um UITabBarController.

Bo A
fonte
0

Eu encontrei uma solução e escrevi algo em francês (mas o código está em inglês). aqui

A maneira é adicionar o controlador à visualização da janela (o controlador deve possuir uma boa implementação da função shouldRotate ....).

Vinzius
fonte
-3

Se você estiver usando UIViewControllers, existe este método:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

Retorne NOpara os controladores de visualização contendo as visualizações que você não deseja girar.

Mais informações aqui

Martin Gordon
fonte
1
Isso interrompe a rotação, o que estou procurando é forçar uma rotação de volta para retrato se o usuário estiver em paisagem. Eu adicionei um exemplo acima para dizer o que quero dizer melhor.
Dave Verwer
-3

Eu não acho que isso seja possível fazer em tempo de execução, embora você possa, é claro, apenas aplicar uma transformação de 90 graus à sua IU.

Andrew Grant
fonte
É possível com o código que eu postei originalmente, você nunca sabe quando isso pode parar de funcionar;)
Dave Verwer
-3

É isso que eu uso. (Você recebe alguns avisos de compilação, mas funciona tanto no Simulador quanto no iPhone)

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];
kdbdallas
fonte
Descobri que isso é o suficiente para que funcione no 4.1 também. No entanto, como minha barra de status está definida como oculta, eu ganho um intervalo de 20 pixels no topo :(
Anthony Main
setOrientation é uma API privada e o aplicativo que a usa foi rejeitado.
sozinho em