¿Cómo restringir la autorrotación a una única orientación para algunas vistas, mientras que permite todas las orientaciones en otras?

Esta pregunta se trata de la rotación de dispositivos iOS y múltiples vistas controladas en un UINavigationController. Algunas vistas deben estar limitadas a la orientación vertical , y algunas deben autorrotar libremente . Si intentas crear la configuración más simple con tres vistas, notarás que el comportamiento de autorrotación tiene algunos caprichos muy desagradables. El escenario es, sin embargo, muy simple, así que creo que o no estoy haciendo la implementación de autorrotación correctamente, o me estoy olvidando de algo.

Tengo una aplicación de demostración muy básica que muestra la rareza, e hice un video que lo muestra en acción.

  • Descargue la aplicación (proyecto XCode)
  • Ver las clases como una esencia (bastante larga)
  • Vea el video de la pregunta (YouTube, 2m43s)

La configuración es muy básica: tres controladores de vista denominados FirstViewController , SecondViewController y ThirdViewController extienden un AbstractViewController que muestra una etiqueta con el nombre de la clase y que devuelve SÍ para shouldAutorotateToInterfaceOrientation: cuando el dispositivo está en orientación vertical. SecondViewController anula este método para permitir todas las rotaciones. Las tres clases concretas agregan unos pocos cuadrados de colores para poder navegar entre las vistas al presionar y hacer que los controladores UINavigationController y desconecten el UINavigationController . Hasta ahora, un escenario muy simple, diría yo.

Si sostienes el dispositivo en orientación vertical u horizontal, este es el resultado que no solo me gustaría lograr, sino que también esperaría. En la primera imagen, verá que todas las vistas están “en posición vertical” y, en el segundo, verá que solo el segundo controlador de visualización contra rota la orientación del dispositivo. Para ser claros, debería ser posible navegar desde la segunda vista en modo paisaje hasta la tercera, pero dado que esa tercera solo admite la orientación vertical, solo debe mostrarse en orientación vertical. La forma más fácil de ver si los resultados están bien es mirando la posición de la barra de soporte.

Orientación de vista esperada para el dispositivo en modo verticalOrientación de vista esperada para el dispositivo en modo horizontal

Pero esta pregunta está aquí porque el resultado real es completamente diferente. Dependiendo de la vista en la que se encuentre cuando gira el dispositivo, y dependiendo de la vista a la que vaya a continuación, las vistas no girarán (para ser específico, el método didOrientFromInterfaceOrientation: nunca se llama). Si estás en el paisaje en el segundo y navegas al tercero, tendrá la misma orientación que el segundo (= malo). Sin embargo, si navega desde el segundo hasta el primero, la pantalla cambiará a un modo de retrato “forzado” y la barra del operador estará en la parte superior física del dispositivo, independientemente de cómo lo sostenga. El video muestra esto con más detalle.

Orientación de vista real para el dispositivo en modo horizontal

Mi pregunta es doble:

  1. ¿Por qué la primera vista del controlador rota hacia atrás, pero no la tercera?
  2. ¿Qué debe hacerse para obtener el comportamiento correcto de sus vistas cuando solo quiere que algunas vistas se autorroteen, pero no otras?

Cheers, EP.

EDITAR: Como último recurso antes de darle una recompensa, reescribí por completo esta pregunta para que sea más breve, más clara y, con suerte, más invitante para dar una respuesta.

La respuesta corta es que está usando UINavigationController, y eso no funcionará como usted lo desea. De los documentos de Apple:

¿Por qué mi UIViewController no girará con el dispositivo?

Todos los controladores de vista secundarios en su UITabBarController o UINavigationController no aceptan un conjunto de orientación común.

Para asegurarse de que todos los controladores de vista secundarios giren correctamente, debe implementar shouldAutorotateToInterfaceOrientation para cada controlador de vista que represente cada pestaña o nivel de navegación. Cada uno debe estar de acuerdo con la misma orientación para que esa rotación ocurra. Es decir, todos deberían devolver SÍ para las mismas posiciones de orientación.

Puede leer más sobre los problemas de rotación de vista aquí .

Tendrá que pasar su propia gestión de stack de vista / controlador para lo que quiere hacer.

Cree un bolean en el delegado de aplicaciones para controlar qué orientación desea, por ejemplo, crear un bool para habilitar Portrait y en su controlador de vista, desea permitir que Portrait lo habilite mediante una aplicación compartida.

en su controlador de vista, donde desea habilitar o deshabilitar la orientación que desee.

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

en delegado de la aplicación.

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

Este método se activará cada vez que gire el dispositivo. Según estos BOOL, habilite la orientación que desee.

Hubo una pregunta similar hace unos años con varias respuestas. Aquí hay una respuesta reciente de alguien a esa pregunta:
¿Hay alguna forma documentada de configurar la orientación del iPhone?

Por lo que entiendo, este es un problema que mucha gente tiene y la mayoría de los piratas informáticos son la única forma de solucionarlo. Mire a través de ese hilo si no lo ha visto antes y vea si algo funciona para usted.

En una nota lateral, tuve un problema similar hace un tiempo cuando estaba llamando algo en shouldAutorotate y agregué un código para viewWillAppear para tratar de solucionarlo. Honestamente, no puedo recordar si funcionó y ya no tengo una Mac para probarlo, pero encontré el código y lo pegaré aquí en caso de que me inspire.

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

NO UTILICE ESTE HACK, APPLE RECHAZARÁ LA APLICACIÓN EN BASE AL USO DE ‘API PRIVADA’

En aras de la referencia, dejaré mi respuesta aquí, pero el uso de la API privada no pasará del panel de revisión. Aprendí algo hoy: D Como @younce citó los documentos de Apple correctamente, lo que deseo no se puede lograr con el UINavigationController.

Tenía dos opciones. Primero, podría haber escrito mi propio sustituto de controlador de navegación, con todos los horrores que uno podría haber encontrado mientras lo hacía. En segundo lugar, podría haber pirateado la rotación en los controladores de visualización, usando una función no documentada de UIDevice llamada setOrientation:animated:

Debido a que el segundo fue tentadoramente fácil, fui por ese. Esto es lo que hice. Necesitarás una categoría para suprimir las advertencias del comstackdor sobre el setter que no existe:

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

Luego debe verificar las orientaciones admitidas en viewWillAppear: Junto a los métodos de UIDevice utilizados aquí, también puede forzar la orientación vertical presentando un controlador de vista modal, pero eso sucederá instantáneamente y no se animará, así que esta es mi forma 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]; } } } 

El truco es mantener siempre la orientación del dispositivo interno a la orientación “real” del dispositivo. Si comienzas a cambiar eso, la rotación de tu aplicación estará fuera de balance.

Pero como ahora sé, esta es solo una forma segura de rechazar su aplicación. Entonces la opción número dos es solo una mala opción. Reescribe ese NavigationController, o simplemente haz que todas tus vistas sean compatibles con el mismo conjunto de orientación.

Cheers, EP.

En iOS 6, esto se ha convertido en un problema muy simple. Simplemente crea una clase especial para las vistas que deseas autorrotar. Luego, en su rootVC, agregue esto.

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

Sé que esta es una vieja pregunta, pero pensé que valía la pena mencionarla.