web-dev-qa-db-fra.com

iOS 6: Comment restreindre certaines vues au mode Portrait et en autoriser la rotation par d'autres?

J'ai une application iPhone qui utilise une UINavigationController pour présenter une interface d'exploration: d'abord une vue, puis une autre, jusqu'à quatre niveaux de profondeur. Je souhaite que les trois premières vues soient limitées à l'orientation portrait et que seule la dernière vue soit autorisée à pivoter en paysage. Lorsque vous revenez de la quatrième à la troisième et la quatrième vue était en orientation paysage, je veux que tout retourne en mode portrait.

Dans iOS 5, j'ai simplement défini shouldAutorotateToInterfaceOrientation: dans chacun de mes contrôleurs de vue pour renvoyer YES pour les orientations autorisées. Tout fonctionnait comme décrit ci-dessus, y compris le retour en mode portrait même si le dispositif était maintenu en orientation paysage lors du retour du contrôleur de vue n ° 4 à n ° 3.

Dans iOS 6, tous les contrôleurs de vue basculent en mode paysage, en cassant ceux qui ne le sont pas. Les notes de version iOS 6 disent 

Plus de responsabilités se déplacent vers l'application et le délégué de l'application. À présent, les conteneurs iOS (tels que UINavigationController) ne consultent pas leurs enfants pour déterminer s’ils doivent effectuer une autorotation. [...] Le système demande au contrôleur de vue plein écran le plus en haut (généralement le contrôleur de vue racine) ses orientations d'interface prises en charge chaque fois que le périphérique pivote ou lorsqu'un contrôleur de vue est présenté avec le style de présentation modale plein écran. De plus, les orientations prises en charge ne sont extraites que si ce contrôleur de vue renvoie YES à partir de sa méthode shouldAutorotate. [...] Le système détermine si une orientation est prise en charge en croisant la valeur renvoyée par la méthode supportedInterfaceOrientationsForWindow: de l’application avec la valeur renvoyée par la méthode supportedInterfaceOrientations du contrôleur plein écran le plus en haut.

J'ai donc sous-classé UINavigationController, donné à ma MainNavigationController une propriété booléenne landscapeOK et l'utilisé pour renvoyer les orientations autorisées dans supportedInterfaceOrientations. Puis, dans chacune des méthodes viewWillAppear: de mes contrôleurs de vue, j'ai une ligne comme celle-ci.

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

dire à ma MainNavigationController le comportement souhaité.

Voici la question: Si je passe maintenant à ma quatrième vue en mode portrait et retourne le téléphone, il passe en mode paysage. Maintenant, j'appuie sur le bouton Précédent pour revenir à ma troisième vue, censée fonctionner uniquement en mode portrait. Mais ça ne tourne pas en arrière. Comment puis-je le faire faire?

J'ai essayé

    [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait]

dans la méthode viewWillAppear de mon troisième contrôleur de vue, mais il ne fait rien. Est-ce la mauvaise méthode à appeler ou peut-être le mauvais endroit, ou devrais-je mettre en œuvre le tout d'une manière totalement différente?

98
Harald Bögeholz

J'ai eu le même problème et trouvé une solution qui fonctionne pour moi . Pour le faire fonctionner, il est pas suffisant pour implémenter - (NSUInteger)supportedInterfaceOrientations dans votre UINavigationController . Vous devez également implémenter cette méthode dans votre contrôleur # 3, qui est le premier à être en mode portrait uniquement après avoir ouvert le contrôleur # 4 . J'ai donc le code suivant dans mon UINavigationController:

- (BOOL)shouldAutorotate
{
    return YES;
}

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

Dans le contrôleur de vue n ° 3, ajoutez les éléments suivants:

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

Vous n'avez pas besoin d'ajouter quoi que ce soit à vos contrôleurs de vue n ° 1, n ° 2 et n ° 4 .Cela fonctionne pour moi, j'espère que cela vous aidera.

95
Flo

Ajouter un CustomNavigationController

Remplacez ces méthodes:

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

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

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

Maintenant, ajoutez toutes les orientations dans le plist 

enter image description here

Dans le contrôleur de vue, ajoutez uniquement ceux requis:

-(BOOL)shouldAutorotate
{
    return YES;
}

-(NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

ces méthodes remplacent les méthodes du contrôleur de navigation

67
Zaraki

Après avoir examiné chaque réponse dans d'innombrables questions similaires sur SO, aucune des réponses ne m'a fonctionné, mais ils m'ont donné quelques idées. Voici comment j'ai fini par résoudre le problème:

Tout d'abord, assurez-vous que les orientations de l'interface prise en charge dans la cible de votre projet contiennent toutes les orientations souhaitées pour votre vue en rotation.

enter image description here

Ensuite, créez une catégorie de UINavigationController (puisque Apple dit de ne pas la sous-classer):

@implementation UINavigationController (iOS6AutorotationFix)

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

@end

Importez cette catégorie et le contrôleur de vue que vous souhaitez pouvoir faire pivoter (que j'appellerai RotatingViewController) vers votre contrôleur de vue de niveau le plus élevé, qui devrait contenir votre contrôleur de navigation. Dans ce contrôleur de vue, implémentez shouldAutorotate comme suit. Notez que ce ne doit pas être le même contrôleur de vue que vous souhaitez faire pivoter.

-(BOOL)shouldAutorotate {

    BOOL shouldRotate = NO;

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

    return shouldRotate;
}

Enfin, dans votre RotatingViewController, implémentez shouldAutorotate et supportedInterfaceOrientations comme suit:

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

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

Vous devez effectuer cette opération car iOS 6 donne le contrôle de la rotation au contrôleur de vue racine au lieu du contrôleur de vue supérieure. Si vous voulez que la rotation d'une vue individuelle se comporte différemment des autres vues de la pile, vous devez écrire un cas spécifique pour cela dans le contrôleur de vue racine.

9
Brian

Je n'ai pas assez de réputation pour commenter la réponse de @ Brian, je vais donc ajouter ma note ici.

Brian a mentionné qu'iOS6 confiait le contrôle de rotation au rootViewController - il pourrait s'agir non seulement d'un UINavigationController comme mentionné, mais également d'un UITabBarController, ce que c'était pour moi. Ma structure ressemble à ceci:

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

J'ai donc ajouté les méthodes d'abord dans un UITabBarController personnalisé, puis dans un UINavigationController personnalisé et enfin dans le UIViewController spécifique.

Exemple tiré des UITabBarController et 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];
}
4
micmdk

J'aimerais donner une réponse partielle à ma propre question. J'ai trouvé la ligne de code suivante, utilisée dans la méthode viewWillAppear de ma troisième UIViewController, qui fonctionne:

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

Mais je n'aime pas vraiment cette solution. Il utilise une astuce pour attribuer une propriété en lecture seule qui, selon la documentation Apple, représente l'orientation physique du périphérique. C'est comme dire à l'iPhone de sauter dans la bonne orientation dans la main de l'utilisateur.

Je suis très tenté de laisser cela dans mon application car cela fonctionne simplement. Mais cela ne me convient pas, alors j'aimerais laisser la question ouverte pour une solution propre.

3
Harald Bögeholz

C'est ce que j'utilise pour la prise en charge de l'orientation dans iOS 6.0.

-(BOOL)shouldAutorotate{
    return YES;
}  

 - (NSUInteger)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskAll;
}


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

Être que c'est un fil très regardé. Je pensais ajouter ce que je pense être la réponse la plus facile. Cela fonctionne pour iOS 8 et plus

-(BOOL)shouldAutorotate
{
    return YES;
}

et

-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}

C'est tout. Prendre plaisir!

Oh, et mes ViewControllers sont intégrés à un contrôleur de navigation, que je n'avais pas besoin de sous-classer ni de configurer de quelque manière que ce soit.

1
Reeder32

J'ai résolu le même genre de problème.

Si vous utilisez la UINavigationController pour pousser les contrôleurs de vue, vous devez définir les méthodes ci-dessous.

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

À la place de LoginViewController, indiquez la UIViewController que vous voulez afficher. Dans mon cas, je veux montrer le LoginViewController dans le mode Portrait autre ViewControllers en mode landscape.

0
Ramakrishna

Allez dans votre fichier Info.plist et faites le changemententer image description here

Vous pouvez implémenter et substituer les variables shouldAutorotate () et supportedInterfaceOrientations dans toutes vos classes View Controller, qui doivent être présentées dans des orientations différentes de celles définies dans PLIST de votre application.

Cependant, dans une interface utilisateur non triviale, vous pouvez rencontrer un problème pour l'ajouter à des dizaines de classes et vous ne voulez pas toutes les rendre sous-classes communes (MyBaseTableViewController, MyBaseNavigationController et MyBaseTabBarController).

Comme vous ne pouvez pas écraser directement la méthode/var sur UIViewController, vous pouvez le faire sur ses sous-classes qui sont généralement des classes de base de votre type, telles que UITableViewController, UINavigationController et UITabBarController.

Donc, vous pouvez implémenter quelques extensions et toujours configurer MyPreciousViewController pour qu'il s'affiche dans une orientation différente de celle de tous les autres, comme cet extrait de code 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]
    }
}
0
vedrano

Vous voulez forcer le portrait de l'application iOS 6 uniquement, vous pouvez ensuite l'ajouter à une sous-classe UIViewController sous les méthodes 

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

Je voulais que tous mes VCs soient verrouillés en orientation portrait sauf un. C'est ce qui a fonctionné pour moi.

  1. Ajouter un support pour toutes les orientations dans le fichier plist.
  2. Dans le contrôleur de vue racine, détectez le type de contrôleur de vue situé en haut de la fenêtre et définissez l'orientation de l'application en conséquence dans la méthode supportedInterfaceOrientations . Par exemple, je devais faire pivoter mon application uniquement lorsque la vue Web se trouvait au-dessus de la pile. Voici ce que j'ai ajouté dans rootVC: 

    -(NSUInteger)supportedInterfaceOrientations
    {
        UIViewController *topMostViewController = [[Utils getAppDelegate] appNavigationController].topViewController;
        if ([topMostViewController isKindOfClass:[SVWebViewController class]]) {
            return UIInterfaceOrientationMaskAllButUpsideDown;
        }
        return UIInterfaceOrientationMaskPortrait;
    }
    
0
Trunal Bhanse

Cela pourrait ne pas fonctionner pour tout le monde, mais cela fonctionne très bien pour moi. Au lieu de mettre en œuvre ...

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

in viewWillAppear dans tous mes contrôleurs, j'ai décidé de centraliser ce processus dans ma sous-classe UINavigationController en redéfinissant la méthode 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;
    }
}

J'ai constaté que cette méthode de délégation n'est pas appelée lors de l'extraction d'un VC de la pile de navigation. Ce n'était pas un problème pour moi parce que je gère la fonctionnalité de retour à partir de ma sous-classe UINavigationController afin que je puisse définir les boutons et les actions de barre de navigation appropriés pour des VC spécifiques comme celui-ci ...

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(...) {
    ...
}

Voici à quoi ma méthode back ressemble pour gérer le vidage du VC de la pile et définir la préférence de rotation appropriée ...

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

Donc, comme vous pouvez le constater, tout ce qui se passe est que je commence par définir deux propriétés ...

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

in navigationController:willShowViewController:animated: qui déterminera quelles sont les orientations prises en charge dans le document VC sur le point d'être présenté. Lors du popping d'un VC, ma méthode personnalisée "back" est appelée et je règle ensuite isLandscapeOK sur ce qui a été stocké via la valeur previousVCLandscapeOK.

Comme je l'ai dit, cela ne fonctionnera peut-être pas pour tout le monde, mais cela fonctionne très bien pour moi et je n'ai pas à m'inquiéter d'ajouter du code à chacun de mes contrôleurs de vue, j'ai pu tout garder centralisé dans la sous-classe UINavigationController.

J'espère que ça aide quelqu'un comme ça m’a été… .. Merci, Jeremy.

0
Jeremy Fox

Je n'ai pas assez de réputation pour répondre à la question @Ram S sous la réponse @micmdk, je vais donc ajouter ma note ici.

Lorsque vous utilisez UITabbarController , essayez de modifier le code de self.viewControllers.lastObject dans @ Micmdk en self.selectedViewController comme suit:

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