Problèmes avec plusieurs xibs pour la rotation sous iOS6

J'ai besoin d'utiliser différents files xib pour le portrait et le paysage. Je n'utilise pas Auto Layout mais j'utilise iOS6. (Voir ma question précédente si vous vous souciez de pourquoi.)

Je suis la réponse d'Adam à cette question modifiée avec l'astuce de nom initWithNib d'amergin, modifiée avec mes propres besoins iPhone / iPad. Voici mon code:

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { [[NSBundle mainBundle] loadNibNamed:[self xibNameForDeviceAndRotation:toInterfaceOrientation] owner: self options: nil]; [self viewDidLoad]; } - (NSSsortingng *) xibNameForDeviceAndRotation:(UIInterfaceOrientation)toInterfaceOrientation { NSSsortingng *xibName ; NSSsortingng *deviceName ; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { deviceName = @"iPad"; } else { deviceName = @"iPhone"; } if( UIInterfaceOrientationIsLandscape(toInterfaceOrientation) ) { xibName = [NSSsortingng ssortingngWithFormat:@"%@-Landscape", NSSsortingngFromClass([self class])]; if([[NSBundle mainBundle] pathForResource:xibName ofType:@"nib"] != nil) { return xibName; } else { xibName = [NSSsortingng ssortingngWithFormat:@"%@_%@-Landscape", NSSsortingngFromClass([self class]), deviceName]; if([[NSBundle mainBundle] pathForResource:xibName ofType:@"nib"] != nil) { return xibName; } else { NSAssert(FALSE, @"Missing xib"); return nil; } } } else { xibName = [NSSsortingng ssortingngWithFormat:@"%@", NSSsortingngFromClass([self class])]; if([[NSBundle mainBundle] pathForResource:xibName ofType:@"nib"] != nil) { return xibName; } else { xibName = [NSSsortingng ssortingngWithFormat:@"%@_%@", NSSsortingngFromClass([self class]), deviceName]; if([[NSBundle mainBundle] pathForResource:xibName ofType:@"nib"] != nil) { return xibName; } else { NSAssert(FALSE, @"Missing xib"); return nil; } } } } 

et bien sûr je fais:

 - (BOOL) shouldAutorotate { return YES; } - (NSUInteger)supportedInterfaceOrientations { return UIInterfaceOrientationMaskAll; } 

à mon avis controller et:

 - (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window { return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown); } 

dans mon délégué.

J'ai deux problèmes qui peuvent être liés. D'abord, le facile. Je ne tourne pas à l'envers. J'ai tous les bons bits activés dans xcode pour iPad et iPhone. Cela peut être un problème distinct ou peut-être le cœur de mon problème.

Le vrai problème est que lorsque je passe en mode paysage, mon xib est remplacé mais la vue est désactivée de 90 degrés.

Voici à quoi ressemblent mes 2 xib's. (Je les ai coloriés de façon criarde pour que vous puissiez voir qu'ils sont différents.)

entrez la description de l'image ici et entrez la description de l'image ici

et vous pouvez voir quand je l'exécute (initialement en mode Paysage) que le paysage xib est correct.

entrez la description de l'image ici

quand je tourne au portrait, c'est aussi correct

entrez la description de l'image ici

mais quand je returnne au paysage le xib est remplacé mais la vue est éteinte de 90 degrés.

entrez la description de l'image ici

Quel est le problème ici?

J'ai suivi probablement le même path que Paul Cézanne l'année dernière. Je ne sais pas s'il a essayé ou non, mais j'ai résolu le problème d'origine (indiqué dans cette question) en faisant de mon controller racine un controller de navigation au lieu de ma class de controller de vue. Comme j'utilise un model de "projet vide" et des files XIB, cela signifiait changer la normale:

 self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil]; self.window.rootViewController = self.viewController; 

à l'intérieur AppDelegate.m, à la place:

 self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil]; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:self.viewController]; navigationController.navigationBar.hidden = YES; self.window.rootViewController = navigationController; 

C'est-à-dire que je viens de créer un UINavigationController générique et de le définir comme controller de vue racine.

Je ne suis pas sûr si cela causera d'autres problèmes, et il y a probablement un moyen de comprendre (peut-être vous auriez besoin du code source cependant) ce que UINavigationController fait que UIViewController ne fait pas. Pourrait être aussi simple qu'un type supplémentaire d'appel setNeedsLayout au bon endroit. Si je le découvre, je vais éditer cette réponse pour les futurs lecteurs.

Le crédit va aux commentaires de Sakti sur le moyen le plus facile de soutenir plusieurs orientations? Comment charger une NIB personnalisée lorsque l'application est en mode paysage? que je n'aurais pas dû ignorer la première fois que je les lisais:

j'ai ajouté le controller de vue au controller de navigation et l'ai présenté qui l'a fait fonctionner comme prévu

Modifier: Ajout d'une ligne supplémentaire à l'exemple de code pour masquer la barre de navigation, car la plupart des personnes qui suivent ce problème n'en voudront pas.

Voici comment je le fais et cela fonctionne sur iOS 5+:

  - (void)viewDidLoad { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkBagRotation) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; [self checkBagRotation]; } - (void)checkBagRotation { orientation = [UIApplication sharedApplication].statusBarOrientation; if(orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight) { [[NSBundle mainBundle] loadNibNamed:@"Controller-landscape" owner:self options:nil]; } else { [[NSBundle mainBundle] loadNibNamed:@"Controller-portrait" owner:self options:nil]; } 

Je réponds à ma propre question ici en collant de l'article complet sur mon blog iOS à http://www.notthepainter.com/topologically-challenged-ui/

J'ai demandé à un ami de m'aider, il a utilisé 2 vues dans un file xib avec IBOutlets pour la vue portrait et paysage et il a basculé entre elles l'appareil a pivoté. Parfait, non? Eh bien, non, quand vous avez 2 vues dans un XIB, vous ne pouvez pas connecter vos IBOutlets aux deux endroits. Je l'ai fait travailler visuellement, mais mes controls ne fonctionnaient que dans une orientation.

J'ai finalement eu l'idée d'utiliser un controller de vue d'orientation maître qui chargeait les controllers de vue de conteneur lorsque le périphérique tournait. Cela a bien fonctionné. Regardons le code:

 -(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration { if (_timerViewController) { [_timerViewController.view removeFromSuperview]; [_timerViewController willMoveToParentViewController:nil]; [_timerViewController removeFromParentViewController]; self.timerViewController = nil; } self.timerViewController = [[XTMViewController alloc] initWithNibName: [self xibNameForDeviceAndRotation:interfaceOrientation withClass:[XTMViewController class]] bundle:nil]; // use bounds not frame since frame doesn't take the status bar into account _timerViewController.view.frame = _timerViewController.view.bounds = self.view.bounds; [self addChildViewController:_timerViewController]; [_timerViewController didMoveToParentViewController:self]; [self.view addSubview: _timerViewController.view]; } 

AddChildViewController et didMoveToParentViewController doivent être familiers si vous lisez mon article de blog précédent sur Container View Controllers. Cependant, il y a deux choses à noter au-dessus de ces appels. Je vais d'abord traiter le second, j'ai mis l'enfant voir le cadre du controller et les limites des limites des parents, pas de cadre. Ceci pour prendre en count la barre d'état.

Et notez l'appel à xibNameForDeviceAndRotation pour charger le controller de vue à partir de son file xib. Regardons ce code:

 - (NSSsortingng *) xibNameForDeviceAndRotation:(UIInterfaceOrientation)toInterfaceOrientation withClass:(Class) class; { NSSsortingng *xibName ; NSSsortingng *deviceName ; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { deviceName = @"iPad"; } else { deviceName = @"iPhone"; } if( UIInterfaceOrientationIsLandscape(toInterfaceOrientation) ) { xibName = [NSSsortingng ssortingngWithFormat:@"%@-Landscape", NSSsortingngFromClass(class)]; if([[NSBundle mainBundle] pathForResource:xibName ofType:@"nib"] != nil) { return xibName; } else { xibName = [NSSsortingng ssortingngWithFormat:@"%@_%@-Landscape", NSSsortingngFromClass(class), deviceName]; if([[NSBundle mainBundle] pathForResource:xibName ofType:@"nib"] != nil) { return xibName; } else { NSAssert(FALSE, @"Missing xib"); return nil; } } } else { xibName = [NSSsortingng ssortingngWithFormat:@"%@", NSSsortingngFromClass(class)]; if([[NSBundle mainBundle] pathForResource:xibName ofType:@"nib"] != nil) { return xibName; } else { xibName = [NSSsortingng ssortingngWithFormat:@"%@_%@", NSSsortingngFromClass(class), deviceName]; if([[NSBundle mainBundle] pathForResource:xibName ofType:@"nib"] != nil) { return xibName; } else { NSAssert(FALSE, @"Missing xib"); return nil; } } } return nil; } 

Il se passe beaucoup de choses ici. Allons-y. Je détermine d'abord si vous êtes sur un iPhone ou un iPad. Les files xib auront l'iPhone ou l'iPad dans leurs noms. Ensuite nous vérifions pour voir si nous sums en mode paysage. Si c'est le cas, nous construisons une string de test à partir du nom de class, en utilisant la reflection de class via NSSsortingngFromClass. Ensuite, nous utilisons pathForResource pour vérifier si le xib existe dans notre bundle. Si c'est le cas, nous returnnons le nom xib. Si ce n'est pas le cas, nous essayons à nouveau de mettre le nom du périphérique dans le nom xib. Renvoyez-le s'il existe, affirmez un échec si ce n'est pas le cas. Portrait est similaire sauf que par convention nous ne mettons pas "-Portrait" dans le nom xib.

Ce code est assez utile et suffisamment générique pour que je le mette dans mon projet open source EnkiUtils.

Puisque c'est iOS6, nous devons mettre dans le code de la plaque tournante de la rotation iOS6:

 - (BOOL) shouldAutorotate { return YES; } - (NSUInteger)supportedInterfaceOrientations { return UIInterfaceOrientationMaskAll; } 

Curieusement, nous devons également appeler manuellement willAnimateRotationToInterfaceOrientation sur les iPads. Les iPhones obtiennent une willAnimateRotationToInterfaceOrientation automatiquement, mais pas les iPads.

 - (void) viewDidAppear:(BOOL)animated { // iPad's don't send a willAnimate on launch... if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { [self willAnimateRotationToInterfaceOrientation:[[UIApplication sharedApplication] statusBarOrientation] duration:0]; } } 

Alors, avons-nous fini? Embarrassant non. Vous voyez, quand j'ai codé la class XTMViewController j'ai cassé le model de design Model-View-Controller! C'est facile à faire, Apple nous aide déjà en mettant la Vue et le Contrôleur dans la même class. Et il est si facile de mélanger négligemment datatables du model dans le file .hh du VC. Et j'avais fait exactement ça. Quand je cours le code ci-dessus cela fonctionne brillamment, je pourrais le tourner toute la journée et l'interface user était correcte dans les deux orientations. Mais que penses-tu qui est arrivé quand j'ai fait pivoter l'appareil pendant que mes minuteurs d'exercice tournaient? Oui, ils ont tous été supprimés et l'interface user a été réinitialisée à l'état initial. Ce n'était pas du tout ce que je voulais!

J'ai fait une class XTMUser pour contenir toutes datatables de timing, j'ai mis tous les NSTimers dans la class XTMOrientationMasterViewController et ensuite j'ai fait un protocole pour que XTMOrientationMasterViewController puisse répondre aux taps UI dans la class XTMViewController.

Puis j'ai été fait.