¿Existe una forma documentada de configurar la orientación del iPhone?

94

Tengo una aplicación en la que me gustaría admitir la rotación del dispositivo en ciertas vistas, pero otras no tienen mucho sentido en el modo horizontal, por lo que cuando cambio las vistas, me gustaría forzar la rotación para que se establezca en vertical.

Hay un establecedor de propiedades no documentado en UIDevice que hace el truco, pero obviamente genera una advertencia del compilador y podría desaparecer con una revisión futura del SDK.

[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];

¿Existen formas documentadas de forzar la orientación?

Actualización: pensé que proporcionaría un ejemplo ya que no estoy buscando shouldAutorotateToInterfaceOrientation ya que ya lo he implementado.

Quiero que mi aplicación admita el paisaje y el retrato en la Vista 1, pero solo el retrato en la Vista 2. Ya he implementado shouldAutorotateToInterfaceOrientation para todas las vistas, pero si el usuario está en modo horizontal en la Vista 1 y luego cambia a la Vista 2, quiero forzar la teléfono para girar de nuevo a Vertical.

Dave Verwer
fuente
11
Presenté un error al respecto, sugiriendo que Apple exponga la API anterior o respete el SÍ, NO devuelto por el método shouldRotate, cuando se carga una vista por primera vez, no solo cuando el teléfono gira.
rustyshelf
2
He presentado un error pidiendo que se exponga
setOrientation
1
[[UIDevice currentDevice] setOrientation: UIInterfaceOrientationPortrait]; Este método está obsoleto y ya no existe.
Hikmat Khan

Respuestas:

6

Esto ya no es un problema en la versión posterior del iPhone 3.1.2 SDK. Ahora parece respetar la orientación solicitada de la vista que se devuelve a la pila. Eso probablemente significa que necesitaría detectar versiones anteriores del sistema operativo iPhone y solo aplicar setOrientation cuando sea anterior a la última versión.

No está claro si el análisis estático de Apple comprenderá que está trabajando alrededor de las limitaciones del SDK anterior. Apple me ha dicho personalmente que elimine la llamada al método en mi próxima actualización, por lo que aún no estoy seguro de si tener un truco para dispositivos más antiguos pasará el proceso de aprobación.

John K
fuente
No es el caso de iPhone Simulator 3.3.2. Todavía no funciona.
Pato
4
@user ¿Cómo se solicita una orientación cuando se inserta una vista en la pila?
Progrmr
4
Puede sortear el análisis estático de Apple. Utilizo con éxito código indocumentado usando performSelector y confiando en el increíble dinamismo de Obj-C.
Kenneth Ballenegger
@KennethBallenegger Recuerdo haber leído en las pautas y / o el acuerdo que ocultar cosas como esa puede hacer que se elimine su aplicación y tal vez incluso su cuenta. No me arriesgaría a usar una API indocumentada.
Ivan Vučica
101

Esto es mucho después del hecho, pero en caso de que llegue alguien que no esté usando un controlador de navegación y / o no desee usar métodos no documentados:

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

Es suficiente presentar y descartar un controlador de vista de vainilla.

Obviamente, aún deberá confirmar o negar la orientación en su anulación de shouldAutorotateToInterfaceOrientation. Pero esto hará que el sistema vuelva a llamar a shouldAutorotate ...

Josh
fuente
1
Impresionante, esta no está marcada como la respuesta correcta de esta publicación, pero esto resolvió la mitad de mi problema stackoverflow.com/questions/5030315 . El truco de la vista del modelo 'fuerza' la orientación vertical, ¿también conocería uno para forzar el paisaje?
epologee
5
¡¡Ja, ja !! ¡Esto es demasiado estúpido! ¡PERO FUNCIONA! - ¡claro que lo hace! : D
hfossli
Impresionante, he estado luchando con esto durante mucho tiempo. ¡Gracias! mate
Rahul Choudhary
3
Esto parece funcionar tanto para iOS5 como para iOS6: [self presentViewController: [UIViewController nuevo] animado: SIN finalización: ^ {[autodespedidoViewControllerAnimated: SIN finalización: nulo]; }];
Inferis
1
Esto parece funcionar en iOS 7 e iOS 8, pero hay una pantalla negra notable que parpadea a la vista (iOS 8 principalmente).
levigroker
16

Si desea forzarlo a rotar de retrato a paisaje, aquí está el código. Solo tenga en cuenta que necesita ajustar el centro de su vista. Noté que el mío no colocaba la vista en el lugar correcto. De lo contrario, funcionó perfectamente. Gracias por el consejo.

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
fuente
¿También funcionará si agrego un UIAlertView? Intenté CGAffineTransformMakeRotationrotar la vista, pero UIAlertView sigue de acuerdo con la orientación de la barra de estado, es decir, -degreeToRadian (ángulo)? ¿Recibiste algo como esto?
NeverHopeless
La forma más fácil de hacer esto con a UIAlertViewes configurar su controlador de vista como el delegado de alertView y luego en:, - (void) didPresentAlertView:(UIAlertView *) alertViewrealiza la rotación basada en el archivo [UIApplication sharedApplication].statusBarOrientation. Si no lo anima, no debería verse demasiado extraño.
Michael Gaylord
16

Por lo que puedo decir, el setOrientation:método no funciona (o quizás ya no funciona). Esto es lo que estoy haciendo para hacer esto:

primero, ponga esta definición en la parte superior de su archivo, justo debajo de sus #importaciones:

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

luego, en el 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);
}

si desea que esté animado, puede envolver todo en un bloque de animación, así:

[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];

Luego, en su controlador de modo vertical, puede hacer lo contrario: verifique si está actualmente en horizontal y, de ser así, gírelo nuevamente a Vertical.

Bdebeez
fuente
1
En lugar de self.view, debe rotar self.window.view. Esto es útil si tiene UITabBar u otros controladores en la parte superior de su vista.
Borut Tomazin
7

Tenía un problema en el que tenía una UIViewControlleren la pantalla, en una UINavigationController, en orientación horizontal. Sin embargo, cuando el siguiente controlador de vista se introduce en el flujo, necesitaba que el dispositivo volviera a la orientación vertical.

Lo que noté fue que shouldAutorotateToInterfaceOrientation:no se llama al método cuando se inserta un nuevo controlador de vista en la pila, pero se llama cuando se extrae un controlador de vista de la pila .

Aprovechando esto, estoy usando este fragmento de código en una de mis aplicaciones:

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

Básicamente, al crear un controlador de vista vacío, empujarlo a la pila y sacarlo inmediatamente, es posible hacer que la interfaz vuelva a la posición vertical. Una vez que se ha abierto el controlador, simplemente presiono el controlador que pretendía presionar en primer lugar. Visualmente, se ve muy bien: el usuario nunca ve el controlador de vista arbitrario y vacío.

Andrew Vilcsak
fuente
Hola, acabo de implementar su método para forzar un VC en la orientación correcta antes de presionarlo, pero descubrí que solo funciona al forzar orientaciones verticales (si la vista actual es vertical y desea forzar la siguiente en horizontal, ganó 't llamar shouldAutorotateToInterfaceOrientation: al hacer estallar). ¿Ha encontrado una solución para forzar una vista a Paisaje?
Rafael Nobre
Interesante: no he tenido este problema, lo siento. Intentaría jugar con la secuencia de empujones y estallidos, hay una buena posibilidad de que se te ocurra algo.
Andrew Vilcsak
7

Hay una manera simple de forzar programáticamente al iPhone a la orientación necesaria, usando dos de las respuestas ya proporcionadas 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 de maravilla :)

EDITAR:

para iOS 6 necesito agregar esta función: (funciona en el controlador de vista modal)

- (NSUInteger)supportedInterfaceOrientations
{
    return (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight);
}
Guntis Treulands
fuente
Para mí no funciona a las mil maravillas: la barra de navegación y la barra de herramientas se encogen a la altura que tendrían en la vista horizontal. El método para eliminar y volver a agregar la vista superior mantiene la altura correcta, pero también tiene una desventaja: mi barra de navegación se mueve un poco hacia abajo (como si la barra de texto estuviera habilitada)
AlexWien
Funciona en mi extremo, tanto en el controlador de vista modal como en el push. Pero solo en iOS 5. :(
Guntis Treulands
Sí, también lo hice funcionar en ios5. Ahora, en ios6, la altura de la barra de navegación se reduce una vez que se descarta el controlador de vista de paisaje modal
AlexWien
7

He estado investigando y explorando buscando una buena solución para esto. Encontré esta publicación de blog que hace el truco: elimine su vista más externa de la clave UIWindowy agréguela nuevamente, el sistema volverá a consultar los shouldAutorotateToInterfaceOrientation:métodos de sus controladores de vista, imponiendo la orientación correcta que se aplicará. Véalo : iphone obligando a uiview a reorientarse

Rafael Nobre
fuente
Esta solución funcionó mejor para mí. Es importante destacar que la solución que presenta un modalViewController vacío provoca una animación incorrecta al empujar los controladores de vista a la pila. Este método UIWindow no sufre el mismo problema.
Rik Smith-Unna
5

La respuesta de Josh funciona bien para mí.

Sin embargo, prefiero publicar una notificación de "la orientación cambió, actualice la interfaz de usuario" . Cuando un controlador de vista recibe esta notificación, llama shouldAutorotateToInterfaceOrientation:, lo que le permite establecer cualquier orientación al regresar YESa la orientación que desee.

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

El único problema es que esto obliga a una reorientación sin animación. Debería envolver esta línea entre beginAnimations:y commitAnimationspara lograr una transición suave.

Espero que ayude.

Christian Schnorr
fuente
3

FWIW, aquí está mi implementación de la orientación de configuración manual (para ir en el controlador de vista raíz de su aplicación, 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 ];     
}

junto con el siguiente UINavigationControllerDelegatemétodo (asumiendo que estás usando a 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 ];
    }
}

Eso se encarga de rotar la vista raíz según si un entrante UIViewControlleradmite la orientación actual del dispositivo. Finalmente, querrás conectarterotateInterfaceToOrientation a los cambios reales de orientación del dispositivo para imitar la funcionalidad estándar de iOS. Agregue este controlador de eventos al mismo controlador de vista raíz:

-(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 ];
        }
    }
}

Finalmente, regístrese para UIDeviceOrientationDidChangeNotificationrecibir notificaciones en inito me loadviewgusta:

[[ NSNotificationCenter defaultCenter ] addObserver: self
                                           selector: @selector(onUIDeviceOrientationDidChangeNotification:)
                                               name: UIDeviceOrientationDidChangeNotification
                                             object: nil ];
[[ UIDevice currentDevice ] beginGeneratingDeviceOrientationNotifications ];
Henry Cooke
fuente
Esta es la única de las respuestas en esta página que he podido adaptar con éxito al código de mi controlador de navegación. El único problema menor es que la barra de navegación rotada no cambia la altura entre las orientaciones vertical y horizontal como lo haría si se girara mediante el mecanismo normal.
Dan Dyer
@DanDyer, ¿ha resuelto el problema con la altura incorrecta de la barra de navegación? (Especialmente en ios6 la altura es incorrecta)
AlexWien
@AlexWien Finalmente logré convencer al cliente de que no deberíamos estar haciendo esto. Para iOS 6, es posible que deba hacer algo con el nuevo shouldAutorotatemétodo (consulte stackoverflow.com/a/12586002/5171 ).
Dan Dyer
@DanDyer lo resolvió ayer, un simple ocultar y mostrar la navegación y la barra de estado en ViewDidAppear lo resolvió.Ahora todo funciona (SDK ios6, objetivo ios5) Tengo que volver a probar con el objetivo ios6
AlexWien
2

Esto funciona para mí (gracias Henry Cooke):

El objetivo para mí era tratar únicamente con los cambios de orientación del paisaje.

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
fuente
1

Tengo una aplicación en la que me gustaría admitir la rotación del dispositivo en ciertas vistas, pero otras no tienen mucho sentido en el modo horizontal, por lo que cuando cambio las vistas, me gustaría forzar la rotación para que se establezca en vertical.

Me doy cuenta de que la publicación original anterior en este hilo es muy antigua ahora, pero tuve un problema similar, es decir. todas las pantallas de mi aplicación son solo verticales, con la excepción de una pantalla, que el usuario puede girar entre horizontal y vertical.

Esto fue bastante sencillo, pero al igual que otras publicaciones, quería que la aplicación volviera automáticamente al retrato independientemente de la orientación actual del dispositivo, al regresar a la pantalla anterior.

La solución que implementé fue ocultar la barra de navegación en el modo horizontal, lo que significa que el usuario solo puede volver a las pantallas anteriores mientras está en modo vertical. Por lo tanto, todas las demás pantallas solo pueden estar en modo vertical.

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

Esto también tiene el beneficio adicional para mi aplicación de que hay más espacio de pantalla disponible en modo horizontal. Esto es útil porque la pantalla en cuestión se utiliza para mostrar archivos PDF.

Espero que esto ayude.

stephencampbell
fuente
1

Resolví esto con bastante facilidad al final. Probé todas las sugerencias anteriores y aún me quedé corto, así que esta fue mi solución:

En el ViewController que debe permanecer horizontal (izquierda o derecha), escucho los cambios de orientación:

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

Luego en 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];

    }
}

Tenga en cuenta cualquier Supervista / Subvista que use el tamaño automático, ya que view.bounds / frame se está restableciendo explícitamente ...

La única salvedad de este método para mantener la vista horizontal, es el cambio de animación inherente entre las orientaciones que tiene que ocurrir, cuando sería mejor que parezca que no hay cambios.

David van Dugteren
fuente
1

Solución iOS 6:

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

El código exacto depende de la aplicación y también de dónde lo coloque (lo usé en mi AppDelegate). Reemplace [[self window] rootViewController]con lo que usa. Estaba usando un UITabBarController.

Bo A
fuente
0

Encontré una solución y escribí algo en francés (pero el código está en inglés). aquí

La forma es agregar el controlador a la vista de la ventana (el controlador debe poseer una buena implementación de la función shouldRotate ....).

Vinzius
fuente
-3

Si está utilizando UIViewControllers, existe este método:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

Regrese NOpara los controladores de vista que contienen las vistas que no desea rotar.

Más info aquí

Martin Gordon
fuente
1
Eso detiene la rotación, lo que estoy buscando es forzar una rotación de regreso a retrato si el usuario está en paisaje. He agregado un ejemplo arriba para decir mejor lo que quiero decir.
Dave Verwer
-3

No creo que esto sea posible en tiempo de ejecución, aunque, por supuesto, podría aplicar una transformación de 90 grados a su interfaz de usuario.

Andrew Grant
fuente
Es posible con el código que
publiqué
-3

Esto es lo que utilizo. (Obtiene algunas advertencias de compilación pero funciona tanto en el Simulador como en el iPhone)

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];
kdbdallas
fuente
Descubrí que esto es suficiente para que funcione también en 4.1. Sin embargo, debido a que mi barra de estado está configurada como oculta, gano un espacio de 20 píxeles en la parte superior :(
Anthony Main
setOrientation es una API privada y la aplicación que la usa es rechazada.
solo