iOS 6: ¿Cómo puedo restringir algunas vistas al retrato y permitir que otras giren?

99

Tengo una aplicación para iPhone que usa un UINavigationControllerpara presentar una interfaz de desglose: primero una vista, luego otra, hasta cuatro niveles de profundidad. Quiero que las primeras tres vistas se restrinjan a la orientación vertical y solo se debe permitir que la última vista gire a horizontal. Cuando regrese de la cuarta vista a la tercera y la cuarta vista estaba en orientación horizontal, quiero que todo gire de nuevo a retrato.

En iOS 5, simplemente definí shouldAutorotateToInterfaceOrientation:en cada uno de mis controladores de vista para devolver SÍ para las orientaciones permitidas. Todo funcionó como se describió anteriormente, incluido el regreso al retrato incluso si el dispositivo se mantenía en orientación horizontal al regresar del controlador de vista # 4 al # 3.

En iOS 6, todos los controladores de vista giran a paisaje, rompiendo aquellos que no estaban destinados a hacerlo. Las notas de la versión de iOS 6 dicen

Se está trasladando más responsabilidad a la aplicación y al delegado de la aplicación. Ahora, los contenedores de iOS (como UINavigationController) no consultan a sus hijos para determinar si deben rotar automáticamente. [...] El sistema solicita al controlador de vista de pantalla completa superior (generalmente el controlador de vista raíz) sus orientaciones de interfaz compatibles cada vez que el dispositivo gira o siempre que se presenta un controlador de vista con el estilo de presentación modal de pantalla completa. Además, las orientaciones admitidas se recuperan solo si este controlador de vista devuelve YES de su shouldAutorotatemétodo. [...] El sistema determina si una orientación es compatible mediante la intersección del valor devuelto por el supportedInterfaceOrientationsForWindow:método de la aplicación con el valor devuelto por el supportedInterfaceOrientationsmétodo del controlador de pantalla completa superior.

Así que hice una subclase UINavigationController, le di a mi MainNavigationControlleruna propiedad booleana landscapeOKy la usé para devolver las orientaciones permitidas en supportedInterfaceOrientations. Luego, en cada uno de los viewWillAppear:métodos de mis controladores de vista , tengo una línea como esta

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

para decirle a mi MainNavigationControllerel comportamiento deseado.

Aquí viene la pregunta: si ahora navego a mi cuarta vista en modo vertical y doy la vuelta al teléfono, gira a horizontal. Ahora presiono el botón Atrás para regresar a mi tercera vista, que se supone que funciona solo en retrato. Pero no gira hacia atrás. ¿Cómo hago para que haga eso?

Lo intenté

    [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait]

en el viewWillAppearmétodo de mi tercer controlador de vista, pero no hace nada. ¿Es este el método incorrecto para llamar o tal vez el lugar incorrecto para llamarlo o debería implementar todo de una manera totalmente diferente?

Harald Bögeholz
fuente

Respuestas:

95

Tuve el mismo problema y encontré una solución que funciona para mí. Para que funcione, es no suficiente para aplicar - (NSUInteger)supportedInterfaceOrientationsen su UINavigationController. También debe implementar este método en su controlador n. ° 3, que es el primero en ser solo vertical después de hacer estallar el controlador n. ° 4. Entonces, tengo el siguiente código en mi UINavigationController:

- (BOOL)shouldAutorotate
{
    return YES;
}

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

En la vista del controlador n. ° 3, agregue lo siguiente:

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

No es necesario que agregue nada a los controladores de vista n. ° 1, n. ° 2 y n. ° 4. Esto funciona para mí, espero que te ayude.

Flo
fuente
8
¡Resolviste algo que la gente estaba tratando de resolver durante meses! ¡Te mereces mucho crédito por esto! Agregué supportedInterfaceOrientations a una categoría en UIViewController y funciona perfectamente como predeterminado para esos controladores de solo retrato.
Dwery
3
Entre 100 respuestas diferentes sobre SO, esta es la que realmente funciona. Gracias :-)
Christer Nordvik
16
Tengo esta configuración y sigo obteniendo una orientación incorrecta cuando retrocedo del retrato a un controlador de vista restringido solo al paisaje. No gira automáticamente al paisaje. ¿Alguien más lo sigue viendo?
Oded Ben Dov
1
¿Qué pasa si el controlador de vista horizontal deseado se presenta desde un segue y no hay controlador de navegación?
jesses.co.tt
1
Edite el último para que sea NSUInteger como tipo de retorno.
absesivo
68

Agregar un CustomNavigationController

Anula estos métodos en él:

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

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

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

Ahora agregue todas las orientaciones en el plist

ingrese la descripción de la imagen aquí

En el controlador de vista agregue solo los requeridos:

-(BOOL)shouldAutorotate
{
    return YES;
}

-(NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

estos métodos anulan los métodos del controlador de navegación

Zaraki
fuente
5
¡Realmente lo clavaste con esta respuesta! Muchas gracias. Realmente ayudaste mucho. Este ha sido un gran problema para mí durante un tiempo. Gran trabajo.
K2Digital
¡Gracias! ¡Me tomó tanto tiempo y energía entenderlo!
bkbeachlabs
Eres un salvavidas ... Esto también funciona en mi aplicación. En realidad, lo que quiero en mi aplicación es que todos ViewControllerestén en modo Retrato y el mío ViewControlleren modo Paisaje y Retrato. También doy soporte tanto en iOS 5.0 como en iOS 6.0 Es por eso que estoy confuso para trabajar pero esta es una gran solución. agrego CustomNavigationController en mi controlador de vista raíz y doy soporte de acuerdo a mis necesidades. Gracias una vez más.
Bhavin_m
Fantástico. Exactamente lo que necesitaba. Gracias.
mszaro
¿Qué pasa si el controlador de vista horizontal deseado se presenta desde un segue y no hay controlador de navegación?
jesses.co.tt
9

Después de revisar cada respuesta en innumerables preguntas similares sobre SO, ninguna de las respuestas funcionó para mí, pero me dieron algunas ideas. Así es como terminé resolviendo el problema:

Primero, asegúrese de que las Orientaciones de interfaz admitidas en el objetivo de su proyecto contengan todas las orientaciones que desea para su vista giratoria.

ingrese la descripción de la imagen aquí

A continuación, cree una categoría de UINavigationController(ya que Apple dice que no se subclasifique):

@implementation UINavigationController (iOS6AutorotationFix)

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

@end

Importe esa categoría y el controlador de vista que desea poder rotar (al que llamaré RotatingViewController) a su controlador de vista de nivel más alto, que debe contener su controlador de navegación. En ese controlador de vista, implemente de la shouldAutorotatesiguiente manera. Tenga en cuenta que este no debería ser el mismo controlador de vista que desea rotar.

-(BOOL)shouldAutorotate {

    BOOL shouldRotate = NO;

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

    return shouldRotate;
}

Finalmente, en su RotatingViewController, implemente shouldAutorotatey de la supportedInterfaceOrientationssiguiente manera:

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

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

La razón por la que necesita hacer esto es porque iOS 6 le da control de rotación al controlador de vista raíz en lugar del controlador de vista superior. Si desea que la rotación de una vista individual se comporte de manera diferente a otras vistas en la pila, debe escribir un caso específico para ella en el controlador de vista raíz.

Brian
fuente
2
Esta es una excelente respuesta que merece más amor. Aquí está la información que contiene que no pude encontrar en ningún otro lugar, y que es fundamental: si desea que la rotación de una vista individual se comporte de manera diferente a otras vistas en la pila, debe escribir un caso específico en la raíz Ver controlador
shmim
Supongo que esto funcionará si tiene un navigationController como el primer miembro del guión gráfico de su aplicación como controlador inicial, pero ¿qué pasa con el caso cuando el controlador inicial es simplemente un ViewController?
Duck
4

No tengo suficiente reputación para comentar la respuesta de @ Brian, así que agregaré mi nota aquí.

Brian mencionó que iOS6 le da el control de rotación al rootViewController; esto no solo podría ser un UINavigationController como se mencionó, sino también un UITabBarController, que era para mí. Mi estructura se ve así:

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

Así que agregué los métodos primero en un UITabBarController personalizado, luego en un UINavigationController personalizado y finalmente en el UIViewController específico.

Ejemplo de UITabBarController y 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
fuente
De hecho, usé un UITabBarController como variable de instancia en mi controlador de vista raíz porque se supone que no debes subclase UITabBarController en versiones de iOS anteriores a 6.0 y necesitaba admitir iOS 5.0.
Brian
@micmdk tengo el mismo problema. por favor avise cómo resolverlo, he copiado su código y lo puse en el controlador de navegación personalizado y customUabbarController y configuré otro controlador de vista como Brian's Guided. pero aún no obtengo el controlador de vista fijo en retrato.
Ram S
@Brian tengo el mismo problema. por favor avise cómo resolverlo, he copiado su código y lo puse en el Controlador de navegación personalizado y el Controlador Uabbar personalizado y configuré otro controlador de vista como Guiado de Micmdk. pero aún no obtengo el controlador de vista fijo en retrato en ios8.
Ram S
3

Me gustaría dar una respuesta parcial a mi propia pregunta. Encontré la siguiente línea de código, utilizada en el viewWillAppearmétodo de mi tercero UIViewController, para funcionar:

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

Pero realmente no me gusta esta solución. Utiliza un truco para asignar una propiedad de solo lectura que, según la documentación de Apple, representa la orientación física del dispositivo. Es como decirle al iPhone que salte a la orientación correcta en la mano del usuario.

Estoy muy tentado a dejar esto en mi aplicación, ya que simplemente funciona. Pero no se siente bien, así que me gustaría dejar la pregunta abierta para una solución clara.

Harald Bögeholz
fuente
4
Es muy probable que su aplicación sea rechazada si la deja allí. También estoy buscando una solución a esto, pero parece que no es posible. Todo lo que pude encontrar es la respuesta a esta pregunta: stackoverflow.com/questions/7196300/ ... de alguna manera funciona, pero por alguna razón rompe el manejo táctil del botón en uno de mis controladores de vista
Lope
Me arriesgué y me presenté a Apple. Aún esperando revisión ... Lo mantendré informado.
Harald Bögeholz
Apple acaba de aprobar mi aplicación con este truco en su lugar, ¡sí! Espero que no me resulte contraproducente en una futura actualización de iOS. Mientras tanto, ¡todavía estoy abierto a sugerencias para una solución limpia!
Harald Bögeholz
1
@ Sugerencia de HaraldBögeholz Grt. pero cuando estoy trabajando en un nuevo xCode con ARC, no puedo usar el ans. obtengo "El elenco de NSInteger (también conocido como int) a la identificación no está permitido con ARC PerformSelector puede causar una fuga porque su selector es desconocido" ¿cómo puedo resolver esto? Estoy realmente agradecido con usted si tenemos una solución. Cuando apago ARC para una vista particular, la aplicación se bloquea debido a algunos datos de fuga. Por favor
sugiéreme
4
Usar una API privada NUNCA es la solución.
Javier Soto
2

Esto es lo que estoy usando para soporte de orientación en ios 6.0

-(BOOL)shouldAutorotate{
    return YES;
}  

 - (NSUInteger)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskAll;
}


- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{  
  return UIInterfaceOrientationPortrait;
}
Ashisht
fuente
1

Siendo que este es un hilo muy visto. Pensé que agregaría lo que creo que es la respuesta más fácil. Esto funciona para ios8 y posteriores

-(BOOL)shouldAutorotate
{
    return YES;
}

y

-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}

Eso es. ¡Disfrutar!

Ah, y mis ViewControllers están integrados en un controlador de navegación, que no necesitaba subclasificar ni configurar de ninguna manera.

Reeder32
fuente
0

Puede que esto no funcione para todos, pero funciona muy bien para mí. En lugar de implementar ...

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

en viewWillAppear en todos mis controladores, decidí centralizar este proceso dentro de mi subclase UINavigationController anulando el 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;
    }
}

Descubrí que este método de delegado no se llama al sacar un VC de la pila de navegación. Esto no fue un problema para mí porque estoy manejando la funcionalidad de retroceso desde mi subclase UINavigationController para que pueda configurar los botones y acciones de la barra de navegación adecuados para VC 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(...) {
    ...
}

Así es backcomo se ve mi método para manejar la extracción del VC de la pila y establecer la preferencia de rotación adecuada ...

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

Entonces, como puede ver, básicamente todo lo que está sucediendo es que primero me estoy configurando dos propiedades ...

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

en el navigationController:willShowViewController:animated:cual se determinarán cuáles son las orientaciones soportadas dentro de esa CV que está a punto de ser presentada. Al abrir un VC, se llama a mi método personalizado "back" y luego estoy configurando isLandscapeOK a lo que se almacenó a través del valor VCLandscapeOK anterior.

Como dije, esto puede no funcionar para todos, pero funciona muy bien para mí y no tengo que preocuparme por agregar código a cada uno de mis controladores de vista, pude mantenerlo todo centralizado en la subclase UINavigationController.

Espero que esto ayude a alguien como a mí. Gracias Jeremy.

Jeremy Fox
fuente
0

Vaya a su archivo Info.plist y realice el cambioingrese la descripción de la imagen aquí

Nekelle Latoya Celestine
fuente
0

Solo desea Forzar el retrato de la aplicación iOS 6 y luego puede agregar a una subclase de UIViewController a continuación los métodos

- (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
fuente
2
El problema es que, a menos que esa vista sea la vista raíz (que no está en esta pregunta), no se llama a shouldAutorotate.
Brian
por alguna razón, el tipo de retorno NSUIntegerarroja una advertencia a pesar de ser el tipo var correcto. si tiene TOC, use UIInterfaceOrientationMasken su lugar.
Chris J
Además, esta solución funcionó perfectamente para un controlador de vista modal (una vista de cámara) que tengo sin ninguna necesidad de subclasificar UINavigationController. Supongo que los modales se tratan de forma independiente. No es relevante para la pregunta original, pero información útil para tener un diseño inteligente.
Chris J
0

Quería tener todos mis VC bloqueados en orientación vertical excepto uno. Esto es lo que funcionó para mí.

  1. Agregue soporte para todas las orientaciones en el archivo plist.
  2. En el controlador de vista raíz, detecte el tipo de controlador de vista que está en la parte superior de la ventana y establezca la orientación de la aplicación en consecuencia en el método supportedInterfaceOrientations . Por ejemplo, necesitaba que mi aplicación girara solo cuando la vista web estaba en la parte superior de la pila. Esto es lo que agregué en mi rootVC:

    -(NSUInteger)supportedInterfaceOrientations
    {
        UIViewController *topMostViewController = [[Utils getAppDelegate] appNavigationController].topViewController;
        if ([topMostViewController isKindOfClass:[SVWebViewController class]]) {
            return UIInterfaceOrientationMaskAllButUpsideDown;
        }
        return UIInterfaceOrientationMaskPortrait;
    }
Trunal Bhanse
fuente
Trunal, esto tiene algunos meses, pero ¿puede darnos un poco más de detalle sobre cómo detecta el tipo de controlador de vista que está en la parte superior? Específicamente, no estoy seguro de lo que tienes aquí [[Utils getAppDelegate] appNavigationController]. Utils Supongo que es una clase que tienes, pero no sé qué haces en ella. ¡Cualquier ayuda sería genial!
Ben
0

No tengo suficiente reputación para responder la pregunta de @Ram S en la respuesta de @micmdk, así que agregaré mi nota aquí.

Cuando use UITabbarController , intente cambiar self.viewControllers.lastObject en el código de @ micmdk a self.selectedViewController así:

- (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
fuente
0

Puede implementar y anular shouldAutorotate () y supportedInterfaceOrientations var en todas sus clases de View Controller que deben presentarse en orientaciones diferentes a las definidas en PLIST de su aplicación.

Sin embargo, en una interfaz de usuario no trivial, es posible que tenga un problema para agregarlo en docenas de clases y no desea que todas sean subclase de varios comunes que lo admiten (MyBaseTableViewController, MyBaseNavigationController y MyBaseTabBarController).

Dado que no puede anular esos métodos / var en UIViewController directamente, puede hacerlo en sus subclases que generalmente son clases base suyas como UITableViewController, UINavigationController y UITabBarController.

Por lo tanto, puede implementar algunas extensiones y aún configurar MyPreciousViewController para que se muestre en orientaciones diferentes a todas las demás, como este fragmento de código 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
fuente
0

He resuelto el mismo tipo de problema.

Si está utilizando UINavigationControllerpara empujar los controladores de vista, debe configurar los métodos a continuación.

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

En el lugar de LoginViewControlleruso que UIViewControllerdesea mostrar. En mi caso, quiero mostrar el LoginViewControlleren el Portraitmodo otro ViewControllersen el landscapemodo.

Ramakrishna
fuente
-1

Utilice el siguiente método para resolver este problema

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

devuelve solo la orientación que quieras !!!

maxfiresolutions
fuente
3
Hice eso, como escribí. Esto evita que mi controlador de vista gire, pero no lo obliga a volver a la única orientación admitida cuando vuelvo a él.
Harald Bögeholz
Esto no me funciona. Incluso habiendo incluido esto en mi controlador de vista, la vista aún gira. ¿Lo que da?
calce el
¿Por qué votar en contra de maxfiresolutions? Me funcionó y el mismo método dado por @Flo es tener muchos votos a favor.
ViruMax