web-dev-qa-db-fra.com

shouldAutorotateToInterfaceOrientation n'étant pas appelé dans iOS 6

J'utilise MGSplitViewController et j'utilise shouldAutorotateToInterfaceOrientation pour contrôler la taille du contrôleur de vue principal en rotation. 

Tout fonctionne bien sur iOS 5, mais sur iOS 6 (simulateur et iPad), shouldAutorotateToInterfaceOrientation n'est jamais appelé.

Est-ce un bug que je devrais m'attendre à résoudre avec la version finale d'iOS 6 ou dont quelque chose dont je ne suis pas au courant a changé?

60
Hesham Abd-Elmegid

VEUILLEZ LIRE ATTENTIVEMENT ceci ou vous pourriez perdre 1 à 2 jours de votre vie en tombant dans les noix et en vous battant, en tremblant, en tournant votre appareil de test comme le chimpanzé du Zoo qui a attrapé le portable d'un visiteur! Tôt ou tard ... promis :)

DANS iOS 6

shouldAutorotateToInterfaceOrientation:

est obsolète et remplacé par

shouldAutorotate

cela signifie que iOS 6 n'appellera jamais shouldAutorotateToInterfaceOrientation:

donc si vous avez utilisé ce qui suit dans votre application

AVANTiOS6 (iOS5, iOS4, etc.)

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if (interfaceOrientation == UIInterfaceOrientationPortrait) {
// your code for portrait mode

}

return YES;
}

tu devrais utiliser

APRÈSiOS 6+

- (BOOL)shouldAutorotate {

UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];

if (orientation == UIInterfaceOrientationPortrait) {
// your code for portrait mode

}

return YES;
}

ÊTRE CONSCIENT

UIInterfaceOrientationest une propriété de UIApplication et ne contient que 4 possibilités correspondant à l'orientation de la barre d'état:

UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,

UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,

UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,

UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft

Ne pas le confondre avec

UIDeviceOrientationqui est une propriété de la classe UIDevice et contient 7 valeurs possibles:

UIDeviceOrientationUnknown - Can not be determined

UIDeviceOrientationPortrait - Home button facing down

UIDeviceOrientationPortraitUpsideDown - Home button facing up

UIDeviceOrientationLandscapeLeft - Home button facing right

UIDeviceOrientationLandscapeRight - Home button facing left

UIDeviceOrientationFaceUp - Device is flat, with screen facing up

UIDeviceOrientationFaceDown - Device is flat, with screen facing down

même si vous pouvez théoriquement utiliserUIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];qui renvoie UIDeviceOrientation - l'orientation réelle du périphérique - MAIS vous devez savoir que UIDeviceOrientation n'est pas toujours égal à UIInterfaceOrientation !!! Par exemple, lorsque votre appareil est sur une table standard, vous pouvez recevoir une valeur inattendue.

Vous pouvez utiliserUIInterfaceOrientation orientation = self.interfaceOrientation;too qui renvoie UIInterfaceOrientation, l'orientation actuelle de l'interface, MAIS c'est une propriété de UIViewController, de sorte que vous ne pouvez accéder à celle-ci que dans les classes UIViewController.

Si vous souhaitez prendre en charge les appareils iOS6 antérieurs (iOS3/4/5) et iOS6 (ce qui pourrait être évident), utilisez simplement shouldAutorotateToInterfaceOrientation: et shouldAutorotate dans votre code.

Sous iOS 6 il existe 3 niveaux et 3 étapes que l'appareil vérifie lors du lancement de l'application, que vous devez contrôler si vous le souhaitez.

1. Info.plist - Supported Interface Orientations

que vous pouvez définir graphiquement dans l'onglet Résumé. La séquence des orientations autorisées est IMPORTANT. Vous pouvez la modifier manuellement en éditant le info.plist. Le périphérique choisira le premier lors du lancement de l'application! Ceci est essentiel car le problème est toujours le lancement de l'application lorsqu'il existe un risque que le [UIDevice currentDevice].orientation soit inconnu, en particulier lorsque nous testons notre application sur une surface plane.

plist setting in XCode (Info)

BE AWARE Il y a deux autres possibilités de paramètres avec l'extension (iPad) ou (iPhone), si vous en utilisez un, il utilisera ce paramètre de l'appareil ou du simulateur actuel et négligera les paramètres généraux sans extension. Ainsi, si vous exécutez une application uniquement pour iPhone et que vous avez accidentellement laissé une ligne "Supported Interface Orientations (iPad)" quelque part dans le plist, même sans aucune donnée, les règles définies précédemment dans les paramètres généraux (dans mon exemple pour iPhone, seront négligées). ) et vous pourriez obtenir un rejet de votre application avec un texte "Nous avons constaté que votre application ne remplissait pas les conditions pour fonctionner sur iPad ...", même si votre application n'a pas l'intention d'utiliser une orientation donnée sur iPhone, mais iPad l'utilisera, ce qui pourrait provoquer des erreurs imprévisibles, car toutes les applications pour iPhone doivent également fonctionner sur iPad pendant le processus de soumission.

2. AppDelegate - application:supportedInterfaceOrientationsForWindow

renvoyer une liste de masques de bits de chaque orientation que vous souhaitez autoriser, qui annule les paramètres info.plist.Celle est appelée au moins une fois à chaque rotation de l'appareil.

3. Top-level view controller or RootViewController - supportedInterfaceOrientations

qui donne une intersection avec l'ensemble de l'application et du délégué de l'application, ce qui doit donner un résultat non nul pour éviter un crash. Ceci est appelé au moins une fois à chaque rotation du périphérique, sauf qu'une autre méthode est installée dans notre contrôleur:

shouldAutorotate

qui interfère avec les orientations autorisées de l'application et donne une BOOL avec une valeur par défaut YES.

BE CAREFUL when you use `NavigationController`

en tant que contrôleur supérieur dans votre AppDelegate, comme ceci:

DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil];
UINavigationController *navigationController=[[UINavigationController alloc] initWithRootViewController:detailViewController];
self.window.rootViewController =nil;
self.window.rootViewController = navigationController;
[self.window makeKeyAndVisible];
return YES;

dans ce cas, vous devez placer le code suivant dans votre AppDelegate en tant que catégorie attachée à la classe NavigationController, car il s'agit du contrôleur le plus haut; et si vous n'en avez pas fait une sous-catégorie, vous n'avez pas de lieu/code où vous pouvez définir ses paramètres d’orientation, vous devez donc le forcer à vérifier votre rootViewController réelle dans ce cas la detailViewController pour les orientations:

@implementation UINavigationController (OrientationSettings_IOS6)

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

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

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

@end

après cela, vous pouvez définir les orientations préférées dans votre "variable la plus importante" ViewController (dans mon exemple, il s'agit de la detailViewController) avec l'une des méthodes disponibles dans iOS 6 pour ViewControllers, comme ci-dessous:

1. (BOOL)shouldAutorotate

2. (NSUInteger)supportedInterfaceOrientations

3. (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
127
BootMaker

Ok, je l'ai fait fonctionner dans iOS6 iPad Simulator. Yay. Voici ce que j'ai fait:

Je viens de vous montrer avant et après, cela devrait être explicite:

AVANT

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {

if (interfaceOrientation==UIInterfaceOrientationPortrait) {
 // do some sh!t

}

return YES;
}

APRÈS

- (BOOL)shouldAutorotate {

UIInterfaceOrientation orientation = [[UIDevice currentDevice] orientation];

if (orientation==UIInterfaceOrientationPortrait) {
 // do some sh!t

}

return YES;
}

En ce qui concerne l'orientation prise en charge, vous pouvez soit spécifier dans info.plist en tant que tel: Supported Orientation pic

Ou utilisez le code:

-(NSUInteger)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskPortrait; // etc
}

Édition: À bien y penser, si vous envisagez de prendre en charge les versions inférieures (iOS4.3/5/5.1) ainsi que 6.0, incluez simplement les deux méthodes avec le même contenu de code. Fonctionne pour moi (en sim quand même)

55
GeneCode

Paramétrer le contrôleur de route de la fenêtre de l'application plutôt que d'ajouter simplement sa vue comme une sous-vue fonctionnait pour moi (comme le faisait Rocotilos)

//    [self.window addSubview:self.topLevelNavigationController.view];
self.window.rootViewController = self.topLevelNavigationController;
20
David Neil Wilson

Voici une solution pour iOS 5 et le code antérieur ne nécessitant pas la duplication de votre logique. Il suffit d’ajouter ce code à votre contrôleur de vue pour qu’il génère les éléments supportedInterfaceOrientations à partir de votre méthode shouldAutorotateToInterfaceOrientation existante.

-(BOOL)shouldAutorotate{
    return YES;
}

-(NSInteger)supportedInterfaceOrientations{
NSInteger mask = 0;
if ([self shouldAutorotateToInterfaceOrientation: UIInterfaceOrientationLandscapeRight])
    mask |= UIInterfaceOrientationMaskLandscapeRight;
if ([self shouldAutorotateToInterfaceOrientation: UIInterfaceOrientationLandscapeLeft])
    mask |= UIInterfaceOrientationMaskLandscapeLeft;
if ([self shouldAutorotateToInterfaceOrientation: UIInterfaceOrientationPortrait])
    mask |= UIInterfaceOrientationMaskPortrait;
if ([self shouldAutorotateToInterfaceOrientation: UIInterfaceOrientationPortraitUpsideDown])
    mask |= UIInterfaceOrientationMaskPortraitUpsideDown;
return mask;
}
18
user441669

Une solution rapide pour moi a été d'ajouter ce code sur mon contrôleur de vue racine

- (BOOL)shouldAutorotate {
    return [self shouldAutorotateToInterfaceOrientation:self.interfaceOrientation];
}

De cette façon, j'utilise toujours la même logique que celle utilisée pour les versions antérieures à iOS 6. Bien entendu, j'avais initialement défini mon rootViewController dans les délégués de l'application correctement, au lieu de simplement ajouter des sous-vues Windows.

15
g1de0n_ph

Et bien que la réponse soit indiquée dans un certain nombre de réponses - à implémenter, il convient que AutoritéAutomatique et interfaces d'InterfaceOrientations prises en charge pour iOS 6 - vous devez également savoir que si votre contrôleur de vue est hébergé dans un contrôleur de navigation, cela n'aura aucune importance le runtime les appellera sur l'instance UINavigationController et ignorera votre code autrement.

Apparemment, la solution consiste à sous-classer UINavigationController et à implémenter ces nouvelles méthodes sur cette sous-classe, comme décrit ici: iOS 6 Orientation prise en charge par UITabBarController par iOS avec le contrôleur UINavigation actuel

Et puis vous avez le plaisir de changer tout votre code lorsque vous utilisez un UINavigationController pour utiliser cette nouvelle sous-classe à la place.

Cela doit être l’un des changements les plus inutiles et les plus gênants dans une version iOS que j’ai jamais vu.

11
Christopher King

IOS 5

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
if (UIInterfaceOrientationLandscapeLeft) {
        return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
    }
    if (UIInterfaceOrientationLandscapeRight) {
        return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
    }
    return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}

IOS 6

-(BOOL)shouldAutorotate{
    return YES;
}

-(NSInteger)supportedInterfaceOrientations:(UIWindow *)window{

    //    UIInterfaceOrientationMaskLandscape;
    //    24
    //
    //    UIInterfaceOrientationMaskLandscapeLeft;
    //    16
    //
    //    UIInterfaceOrientationMaskLandscapeRight;
    //    8
    //
    //    UIInterfaceOrientationMaskPortrait;
    //    2


    //    return UIInterfaceOrientationMaskLandscape;
    return 24;
}
10
Roshan Jalgaonkar

Voici une citation du SDK iOS iOS, XCode4.5 + (voir Référence de la classe UIViewController, Gestion des rotations de vues):

Dans iOS 6, votre application prend en charge les orientations d’interface définies dans le fichier Info.plist de votre application. Un contrôleur de vue peut remplacer la méthode supportedInterfaceOrientations pour limiter la liste des orientations prises en charge. Généralement, le système appelle cette méthode uniquement sur le contrôleur de vue racine de la fenêtre ou sur un contrôleur de vue présenté pour remplir la totalité de l'écran; Les contrôleurs de vue enfants utilisent la partie de la fenêtre qui leur est fournie par leur contrôleur de vue parent et ne participent plus directement aux décisions concernant les rotations prises en charge.

Également dans iOS6, la méthode shouldAutorotateToInterfaceOrientation: de la classe UIViewController est déjà obsolète.

Donc, dans votre contrôleur de vue racine, vous faites le ff:

- (BOOL)shouldAutorotate {
  return YES;
}

- (NSUInteger)supportedInterfaceOrientations {
  return UIInterfaceOrientationMaskPortrait;
}

Btw, "contrôleur de vue racine" est toute sous-classe UIViewController que vous définissez comme rootViewController de l'objet window de votre appDelegate. Vous le faites généralement dans la méthode application: didFinishLaunchingWithOptions: de appDelegate.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  // Override point for customization after application launch.
  self.window.rootViewController = [FFDashboardController create];
  [self.window makeKeyAndVisible];
  return YES;
}

Pour utiliser UINavigationController en tant que VC racine, vérifiez réponse de Vladimir .

3
MkVal

En réponse à @Rocotilos, j'ai un addendum dans mon code que je n'ai pas vu ailleurs dans les forums. Je me suis trouvé dans une situation où, au tout début du cycle de vie de l'application, l'orientation n'était pas connue dans la méthode shouldAutorotate. Cela empêchait l'application d'afficher correctement la vue en mode paysage. Après quelques rotations dans le simulateur, tout fonctionnait bien.

Mon scénario est que certaines vues apparaissent sur lesquelles nous souhaiterions une disposition en paysage. En tant que tel, nous ne voudrions pas que l’Autorité décide de retourner OUI. Je me rends compte que cela peut être un cas rare pour certains, mais j'ai passé beaucoup de temps à diagnostiquer cela et je voulais passer. J'espère que c'est utile.

- (BOOL) shouldAutorotate {

    BOOL shouldRotate = NO;
    UIInterfaceOrientation orientation = [[UIDevice currentDevice] orientation];

    if ([self IsCaseWhereWeDontWantLandscapeAutorotation]) {
        shouldRotate = NO;
    } else if (orientation == UIDeviceOrientationUnknown) {
        //Handle the case where the device's orientation is not yet known
        shouldRotate = YES;
    } else {
        //Handle the normal case
        shouldRotate = (orientation == UIInterfaceOrientationMaskLandscape);
    }

    return shouldRotate;
}
3
oddmeter

Cela a fonctionné pour moi avec iOS6 et Xcode 4.5 GM:

Sur AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    .....

    //   self.window.rootViewController = self.navigationController;

    [window setRootViewController:navigationController];

    .....

    return YES;
}

sur ViewController:

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration

{
    if (UIInterfaceOrientationIsLandscape(interfaceOrientation))

    {
      // landscape
    }
    else
    {
      //portrait
    }
}
3
Carlos Moura

Oui, le problème concerne uniquement la méthode window.rootViewController appelée pour renvoyer un masque d'orientation . La méthode So supportedInterfaceOrientations doit être implémentée dans viewController, qui est défini comme window.rootViewController. Mais dans la plupart des cas, cet objet n'est pas de classe personnalisée, par exemple. UINavigationController. Une solution possible consiste à sous-classer UINavigationController. Mais Apple dit: "Cette classe n'est pas destinée au sous-classement", je préférerais donc utiliser un autre UIViewController pour gérer les orientations et ajouter UINavigationController en tant qu'enfant. 

MyRootViewController * myRoot = [MyRootViewController new];
self.window.rootViewController = myRoot;
[myRoot addChildViewController:navigationController];
[myRoot.view addSubview:navigationController.view];

et dans MyRootViewController implémentent des méthodes:

- (BOOL)shouldAutorotate {
 return YES;
}

- (NSUInteger)supportedInterfaceOrientations
{
    // return your mask here e.g.:
    return UIInterfaceOrientationMaskPortrait;
}
2
Vladimir

Il s'avère que seule la vue racine gère ces appels. Dans mon cas, c'était un contrôleur UINavigation normal. J'ai dû changer cela en un fichier sous-classé dans lequel j'ai ajouté les méthodes.

Dans mon cas, je ne voulais que le portrait de la vue racine et le reste du portrait + paysage.

Appdelegate.h

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    // Override point for customization after application launch.
    S2MBookAppRootViewController *masterViewController = [[[S2MBookAppRootViewController alloc] initWithNibName:pref.rootNibName bundle:nil] autorelease];
    self.navigationController = [[[S2MBookAppNavController alloc] initWithRootViewController:masterViewController] autorelease];

    self.window.rootViewController = self.navigationController;


    [self.window makeKeyAndVisible];
    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackOpaque animated:YES];

    return YES;
}

Et le S2MBookAppNavController (sous-classe de UINavigationController)

- (BOOL)shouldAutorotate {
    return YES;
}

- (NSUInteger)supportedInterfaceOrientations
{
    if([self.viewControllers count] == 1) return UIInterfaceOrientationMaskPortrait;
    return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscape;
}

UPDATE: Lorsque vous souhaitez forcer l’orientation (lorsque vous appuyez sur une nouvelle vue du contrôleur de navigation), essayez ceci.

Faites de votre UINavigationController également son propre délégué:

@interface S2MBookAppNavController : UINavigationController <UINavigationControllerDelegate>

@end

Et dans le fichier .m

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.delegate = self;
    // Do any additional setup after loading the view.
}

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
        if ([UIViewController respondsToSelector:@selector(attemptRotationToDeviceOrientation)]) {
            //present/dismiss viewcontroller in order to activate rotating.
            UIViewController *mVC = [[[UIViewController alloc] init] autorelease];
            [self presentModalViewController:mVC animated:NO];
            [self dismissModalViewControllerAnimated:NO];
        }
}
2
ejazz
-(BOOL)shouldAutorotate
{
    UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;

    if (orientation == UIDeviceOrientationUnknown) {
        return YES;
    }

    return [self shouldAutorotateToInterfaceOrientation:self.interfaceOrientation];
}
1
Kumaresan P

Apple a déconseillé d'utiliser la méthode d'authentification de iOS6 à la place. Voir vos docs xcode

- (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0);
- (NSUInteger)supportedInterfaceOrientations NS_AVAILABLE_IOS(6_0);
// Returns interface orientation masks.
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS(6_0);
1
Saad

J'ai une série de contrôleurs de vue dans un UINavigationController, dont l'un devait être uniquement en mode paysage. Je l'ai corrigé en sous-classant UINavigationController et en ajoutant le code suivant:

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

Cela garantit que le contrôleur de vue le plus haut dicte l'orientation.

Cependant, si vous passez d'un vc limité à un paysage et prenant en charge n'importe quelle orientation, la nouvelle vue s'affichera en mode paysage, quelle que soit l'orientation du téléphone . code dans le viewControllers illimité:

- (NSUInteger)supportedInterfaceOrientations{
   if(UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation]))       
      return UIInterfaceOrientationMaskPortrait;
   else
      return UIInterfaceOrientationMaskLandscape;
}
1
Daz Eddy

Les notes sous la rubrique UIKit ici: http://developer.Apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/_index.html%23Apple_ref/doc/uid/TP40012166- CH1-SW19 donnez quelques indices sur la réponse, mais ce n’est pas tout. Je n'ai pas encore l'image complète, mais voici ce que j'ai trouvé jusqu'à présent pour iOS 6.0 RTM.

Si vous ne limitez pas vraiment les orientations prises en charge et si vous souhaitez faire quelque chose parce que l'utilisateur a fait pivoter le périphérique, vous pouvez déplacer la logique dans

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

à

- (void)viewWillLayoutSubviews

au lieu.

Cela peut probablement être un remplacement direct, mais je n'ai pas encore testé les versions iOS de niveau inférieur.

Si vous souhaitez restreindre les orientations prises en charge, vous devez le faire au niveau UIApplicationDelegate avec

-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window

La documentation indique que "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 élevé." mais dans l'expérimentation j'ai trouvé que le système ignore mes contrôleurs de vue pris en charge

-(NSUInteger)supportedInterfaceOrientations

valeur de retour, même si la méthode est appelée.

1

Son travail pour moi:

- (BOOL)shouldAutorotate {
    return YES;
}

- (NSUInteger)supportedInterfaceOrientations {
    NSUInteger orientations = UIInterfaceOrientationMaskPortrait;
    if ([self isKindOfClass:[YourViewController class]]) { // Your view class
        orientations |= UIInterfaceOrientationMaskPortraitUpsideDown;
    }
    return orientations;
}

orientations:

orientations |= UIInterfaceOrientationMaskLandscapeLeft;
orientations |= UIInterfaceOrientationMaskLandscapeRight;
0
Nazir