iOS 7: Contrôleur de vue de conteneur personnalisé et encart de contenu

J'ai un controller de vue de table enveloppé dans un controller de navigation. Le controller de navigation semble appliquer automatiquement l'insertion de contenu correcte au controller de vue de la table lorsqu'il est présenté via presentViewController:animated:completion: (Quelqu'un peut-il m'expliquer comment cela fonctionne exactement?)

Toutefois, dès que j'enveloppe la combinaison dans un controller de vue de conteneur personnalisé et que présente à la place, la partie la plus élevée du contenu de la vue de table est masquée derrière la barre de navigation. Y a-t-il quelque chose que je puisse faire pour préserver le comportement d'encart du contenu automatique dans cette configuration? Dois-je «passer» quelque chose dans le controller de vue de conteneur pour que cela fonctionne correctement?

Je souhaite éviter d'avoir à ajuster l'encart de contenu manuellement ou via Auto Layout car je souhaite continuer à prendre en charge iOS 5.

J'ai eu un problème similaire. Donc, sur iOS 7, il y a une nouvelle propriété sur UIViewController appelée automaticallyAdjustsScrollViewInsets . Par défaut, ceci est défini sur YES . Lorsque la hiérarchie de vos vues est plus compliquée que celle d'un affichage de défilement / table dans un controller de navigation ou de barre d'tabs, cette propriété ne semble pas fonctionner pour moi.

Ce que j'ai fait était ceci: J'ai créé une class de controller de vue de base dont héritent tous mes autres controllers de vue. Ce controller de vue prend ensuite soin de définir explicitement les encarts:

Entête:

 #import <UIKit/UIKit.h> @interface SPWKBaseCollectionViewController : UICollectionViewController @end 

La mise en oeuvre:

 #import "SPWKBaseCollectionViewController.h" @implementation SPWKBaseCollectionViewController - (void)viewDidLoad { [super viewDidLoad]; [self updateContentInsetsForInterfaceOrientation:self.interfaceOrientation]; } - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { [self updateContentInsetsForInterfaceOrientation:toInterfaceOrientation]; [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; } - (void)updateContentInsetsForInterfaceOrientation:(UIInterfaceOrientation)orientation { if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) { UIEdgeInsets insets; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { insets = UIEdgeInsetsMake(64, 0, 56, 0); } else if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { if (UIInterfaceOrientationIsPortrait(orientation)) { insets = UIEdgeInsetsMake(64, 0, 49, 0); } else { insets = UIEdgeInsetsMake(52, 0, 49, 0); } } self.collectionView.contentInset = insets; self.collectionView.scrollIndicatorInsets = insets; } } @end 

Cela fonctionne également pour les vues Web:

 self.webView.scrollView.contentInset = insets; self.webView.scrollView.scrollIndicatorInsets = insets; 

S'il y a une façon plus élégante et plus fiable de le faire, s'il vous plaît faites le moi savoir! Les valeurs de l'incrustation codée en dur ont une mauvaise odeur, mais je ne vois vraiment pas d'autre moyen de conserver la compatibilité avec iOS 5.

FYI au cas où quelqu'un aurait un problème similaire: il semble que automaticallyAdjustsScrollViewInsets est seulement appliqué si votre scrollview (ou tableview / collectionview / webview) est la première vue dans la hiérarchie de leur controller de vue.

J'ajoute souvent un UIImageView en premier dans ma hiérarchie pour avoir une image de fond. Si vous faites cela, vous devez définir manuellement les incrustations de bord de la scrollview dans viewDidLayoutSubviews:

 - (void) viewDidLayoutSubviews { CGFloat top = self.topLayoutGuide.length; CGFloat bottom = self.bottomLayoutGuide.length; UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0); self.collectionView.contentInset = newInsets; } 

Merci à l'indice de Johannes Fahrenkrug, j'ai compris ce qui suit: automaticallyAdjustsScrollViewInsets semble en effet seulement fonctionner comme annoncé lorsque la vue racine du controller de vue enfant est un UIScrollView . Pour moi, cela signifie que l'arrangement suivant fonctionne:

Contrôleur de vue de contenu à l' intérieur du controller de navigation à l' intérieur du controller de vue de conteneur personnalisé .

Bien que ce ne soit pas:

Contrôleur de vue de contenu à l' intérieur Contrôleur de vue de conteneur personnalisé à l' intérieur du controller de navigation .

La deuxième option, cependant, semble plus raisonnable d'un sharepoint vue logique. La première option ne fonctionne probablement que parce que le controller de vue de conteneur personnalisé est utilisé pour attacher une vue au bas du contenu. Si je voulais placer la vue entre la barre de navigation et le contenu, cela ne fonctionnerait pas de cette façon.

UINavigationController appelle la méthode non documentée _updateScrollViewFromViewController: toViewController en transition entre les controllers pour mettre à jour les encarts de contenu. Voici ma solution:

UINavigationController + ContentInset.h

 #import <UIKit/UIKit.h> @interface UINavigationController (ContentInset) - (void) updateScrollViewFromViewController:(UIViewController*) from toViewController:(UIViewController*) to; @end 

UINavigationController + ContentInset.m

 #import "UINavigationController+ContentInset.h" @interface UINavigationController() - (void) _updateScrollViewFromViewController:(UIViewController*) from toViewController:(UIViewController*) to; @end @implementation UINavigationController (ContentInset) - (void) updateScrollViewFromViewController:(UIViewController*) from toViewController:(UIViewController*) to { if ([UINavigationController instancesRespondToSelector:@selector(_updateScrollViewFromViewController:toViewController:)]) [self _updateScrollViewFromViewController:from toViewController:to]; } @end 

Appelez updateScrollViewFromViewController: toViewController quelque part dans votre controller de vue de conteneur personnalisé

 [self addChildViewController:newContentViewController]; [self transitionFromViewController:previewsContentViewController toViewController:newContentViewController duration:0.5f options:0 animations:^{ [self.navigationController updateScrollViewFromViewController:self toViewController:newContentViewController]; } completion:^(BOOL finished) { [newContentViewController didMoveToParentViewController:self]; }]; 

Vous n'avez pas besoin d'appeler des methods non documentées. Tout ce que vous avez à faire est d'appeler setNeedsLayout sur votre UINavigationController . Voir cette autre réponse: https://stackoverflow.com/a/33344516/5488931

Solution rapide

Cette solution cible iOS 8 et supérieur dans Swift.

Le controller de vue racine de mon application est un controller de navigation. Initialement, ce controller de navigation a un UIViewController sur sa stack que j'appellerai le controller de vue parent.

Lorsque l'user appuie sur un button de barre de navigation, le controller de vue parent bascule entre deux controllers de vue enfant à l'aide de l'API de confinement (basculement entre les résultats mappés et les résultats répertoriés). Le controller de vue parent contient des references aux deux controllers de vue enfant. Le deuxième controller de vue enfant n'est créé que lorsque l'user appuie sur le button bascule pour la première fois. Le deuxième controller de vue enfant est un controller de vue de table et présente le problème à l'origine de la barre de navigation, quelle que soit la définition de sa propriété automaticallyAdjustsScrollViewInsets .

Pour corriger cela, j'appelle adjustChild:tableView:insetTop (montré ci-dessous) après que le controller de vue de table enfant a été créé et dans la méthode viewDidLayoutSubviews du controller de vue parent.

La vue de table du controller de vue enfant et topLayoutGuide.length du controller de vue parent sont transmises à la adjustChild:tableView:insetTop comme ceci …

 // called right after childViewController is created adjustChild(childViewController.tableView, insetTop: topLayoutGuide.length) 

La méthode adjustChild …

 private func adjustChild(tableView: UITableView, insetTop: CGFloat) { tableView.contentInset.top = insetTop tableView.scrollIndicatorInsets.top = insetTop // make sure the tableview is scrolled at least as far as insetTop if (tableView.contentOffset.y > -insetTop) { tableView.contentOffset.y = -insetTop } } 

L'instruction tableView.scrollIndicatorInsets.top = insetTop ajuste l'indicateur de défilement sur le côté droit de la vue de la table afin qu'elle commence juste en dessous de la barre de navigation. C'est subtil et est facilement ignoré jusqu'à ce que vous en preniez conscience.

Voici à quoi ressemble viewDidLayoutSubviews

 override func viewDidLayoutSubviews() { if let childViewController = childViewController { adjustChild(childViewController.tableView, insetTop: topLayoutGuide.length) } } 

Notez que tout le code ci-dessus apparaît dans le controller de vue parent.

J'ai compris cela avec l'aide de la réponse de Christopher Pickslay à la question "iOS 7 Table vue ne parvient pas à ajuster automatiquement l'encart contenu".