METTRE À JOUR
Sur la base de la réponse de Tim, j'ai implémenté les éléments suivants dans chaque contrôleur de vue comportant un scrollview (ou une sous-classe) faisant partie de mon conteneur personnalisé:
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (parent) {
CGFloat top = parent.topLayoutGuide.length;
CGFloat bottom = parent.bottomLayoutGuide.length;
// this is the most important part here, because the first view controller added
// never had the layout issue, it was always the second. if we applied these
// Edge insets to the first view controller, then it would lay out incorrectly.
// first detect if it's laid out correctly with the following condition, and if
// not, manually make the adjustments since it seems like UIKit is failing to do so
if (self.collectionView.contentInset.top != top) {
UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
self.collectionView.contentInset = newInsets;
self.collectionView.scrollIndicatorInsets = newInsets;
}
}
[super didMoveToParentViewController:parent];
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
J'ai un contrôleur de vue de conteneur personnalisé appelé SegmentedPageViewController
. Je règle ceci comme un UINavigationController's rootViewController
.
SegmentedPageViewController
a pour objet de permettre à une UISegmentedControl
, définie comme titleView de NavController, de basculer entre différents contrôleurs de vue enfant.
Ces contrôleurs de vue enfant contiennent tous une vue scrollview, tableview ou collection.
Nous constatons que le premier contrôleur de vue se charge correctement, correctement positionné sous la barre de navigation. Mais lorsque nous passons à un nouveau contrôleur de vues , La barre de navigation n'est pas respectée et la vue est définie sous la barre de navigation.
Nous utilisons la mise en page automatique et le constructeur d'interface. Nous avons essayé tout ce que nous pouvions imaginer, mais nous n’avons pas trouvé de solution cohérente.
Voici le bloc de code principal responsable de la configuration du premier contrôleur de vue et du passage à un autre lorsqu'un utilisateur appuie sur le contrôle segmenté:
- (void)switchFromViewController:(UIViewController *)oldVC toViewController:(UIViewController *)newVC
{
if (newVC == oldVC) return;
// Check the newVC is non-nil otherwise expect a crash: NSInvalidArgumentException
if (newVC) {
// Set the new view controller frame (in this case to be the size of the available screen bounds)
// Calulate any other frame animations here (e.g. for the oldVC)
newVC.view.frame = self.view.bounds;
// Check the oldVC is non-nil otherwise expect a crash: NSInvalidArgumentException
if (oldVC) {
// **** THIS RUNS WHEN A NEW VC IS SET ****
// DIFFERENT FROM FIRST VC IN THAT WE TRANSITION INSTEAD OF JUST SETTING
// Start both the view controller transitions
[oldVC willMoveToParentViewController:nil];
[self addChildViewController:newVC];
// Swap the view controllers
// No frame animations in this code but these would go in the animations block
[self transitionFromViewController:oldVC
toViewController:newVC
duration:0.25
options:UIViewAnimationOptionLayoutSubviews
animations:^{}
completion:^(BOOL finished) {
// Finish both the view controller transitions
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:self];
// Store a reference to the current controller
self.currentViewController = newVC;
}];
} else {
// **** THIS RUNS WHEN THE FIRST VC IS SET ****
// JUST STANDARD VIEW CONTROLLER CONTAINMENT
// Otherwise we are adding a view controller for the first time
// Start the view controller transition
[self addChildViewController:newVC];
// Add the new view controller view to the view hierarchy
[self.view addSubview:newVC.view];
// End the view controller transition
[newVC didMoveToParentViewController:self];
// Store a reference to the current controller
self.currentViewController = newVC;
}
}
}
Votre contrôleur de vue de conteneur personnalisé devra ajuster la contentInset
du deuxième contrôleur de vue en fonction de la hauteur de votre barre de navigation connue, en respectant la propriété automaticallyAdjustsScrollViewInsets
du contrôleur de vue enfant. (Vous pouvez également être intéressé par la propriété topLayoutGuide
de votre conteneur - assurez-vous qu'elle renvoie la bonne valeur pendant et après le changement de vue.)
UIKit est remarquablement incohérent (et bogué) dans la façon dont il applique cette logique; vous verrez parfois qu'il effectuera cet ajustement automatiquement en atteignant plusieurs contrôleurs de vue dans la hiérarchie, mais souvent après un changement de conteneur personnalisé, vous devrez effectuer le travail vous-même.
Cela semble être beaucoup plus simple que ce que les gens font.
UINavigationController définira les incrustations scrollview uniquement lors de la mise en page des sous-vues. addChildViewController:
ne provoque pas de disposition, cependant, après l'avoir appelée, il vous suffit d'appeler setNeedsLayout
sur votre navigationController. Voici ce que je fais lorsque je change de vue dans une vue personnalisée, semblable à un onglet:
[self addChildViewController:newcontroller];
[self.view insertSubview:newview atIndex:0];
[self.navigationController.view setNeedsLayout];
La dernière ligne provoquera le recalcul des incrustations scrollview pour le contenu du nouveau contrôleur de vue.
FYI au cas où quelqu'un aurait un problème similaire: ce problème peut se produire même sans les contrôleurs de vue intégrés. Il semble que automaticallyAdjustsScrollViewInsets
ne soit appliqué que si votre scrollview (ou tableview/collectionview/webview) est la première vue dans la hiérarchie de leur contrôleur de vue.
J'ajoute souvent d'abord un UIImageView dans ma hiérarchie afin d'avoir une image de fond. Si vous faites cela, vous devez définir manuellement les incrustations Edge de la vue de défilement 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;
}
J'ai trouvé une meilleure solution, utilisez la méthode non documentée de UINavigationController.
#import <UIKit/UIKit.h>
@interface UINavigationController (ContentInset)
- (void) computeAndApplyScrollContentInsetDeltaForViewController:(UIViewController*) controller;
@end
#import "UINavigationController+ContentInset.h"
@interface UINavigationController()
- (void)_computeAndApplyScrollContentInsetDeltaForViewController:(id)arg1;
@end
@implementation UINavigationController (ContentInset)
- (void) computeAndApplyScrollContentInsetDeltaForViewController:(UIViewController*) controller
{
if ([UINavigationController instancesRespondToSelector:@selector(_computeAndApplyScrollContentInsetDeltaForViewController:)])
[self _computeAndApplyScrollContentInsetDeltaForViewController:controller];
}
@end
alors, fais comme ça
- (void) cycleFromViewController: (UIViewController*) oldC
toViewController: (UIViewController*) newC
{
[oldC willMoveToParentViewController:nil];
[self addChildViewController:newC];
[self transitionFromViewController: oldC toViewController: newC
duration: 0.25 options:0
animations:^{
newC.view.frame = oldC.view.frame;
[self.navigationController computeAndApplyScrollContentInsetDeltaForViewController:newC];
}
completion:^(BOOL finished) {
[oldC removeFromParentViewController];
[newC didMoveToParentViewController:self];
}];
}
La définition de edgesForExtendedLayout = []
sur mes contrôleurs enfants a fonctionné pour moi.