J'ai un UIPageViewController
avec une barre d'état translucide et une barre de navigation. Son topLayoutGuide
est de 64 pixels, comme prévu.
Cependant, les contrôleurs de vue enfant du UIPageViewController
signalent un topLayoutGuide
de 0 pixels, même s'ils sont affichés sous la barre d'état et la barre de navigation.
Est-ce le comportement attendu? Si oui, quelle est la meilleure façon de positionner une vue d'un contrôleur de vue enfant sous le vrai topLayoutGuide
?
(à moins d'utiliser parentViewController.topLayoutGuide
, que je considérerais comme un hack)
Bien que cette réponse soit peut-être correcte, je me suis quand même retrouvé à parcourir l'arbre de confinement pour trouver le bon contrôleur de vue parent et obtenir ce que vous décrivez comme le "vrai topLayoutGuide
". De cette façon, je peux implémenter manuellement automaticallyAdjustsScrollViewInsets
.
Voici comment je le fais:
Dans mon contrôleur de vue de table (une sous-classe de UIViewController
en fait), j'ai ceci:
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
_tableView.frame = self.view.bounds;
const UIEdgeInsets insets = (self.automaticallyAdjustsScrollViewInsets) ? UIEdgeInsetsMake(self.ms_navigationBarTopLayoutGuide.length,
0.0,
self.ms_navigationBarBottomLayoutGuide.length,
0.0) : UIEdgeInsetsZero;
_tableView.contentInset = _tableView.scrollIndicatorInsets = insets;
}
Remarquez les méthodes de catégorie dans UIViewController
, voici comment je les ai implémentées:
@implementation UIViewController (MSLayoutSupport)
- (id<UILayoutSupport>)ms_navigationBarTopLayoutGuide {
if (self.parentViewController &&
![self.parentViewController isKindOfClass:UINavigationController.class]) {
return self.parentViewController.ms_navigationBarTopLayoutGuide;
} else {
return self.topLayoutGuide;
}
}
- (id<UILayoutSupport>)ms_navigationBarBottomLayoutGuide {
if (self.parentViewController &&
![self.parentViewController isKindOfClass:UINavigationController.class]) {
return self.parentViewController.ms_navigationBarBottomLayoutGuide;
} else {
return self.bottomLayoutGuide;
}
}
@end
J'espère que cela t'aides :)
vous pouvez ajouter une contrainte dans le storyboard et la modifier dans viewWillLayoutSubviews
quelque chose comme ça:
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
self.topGuideConstraint.constant = [self.parentViewController.topLayoutGuide length];
}
Je me trompe peut-être, mais à mon avis, le comportement est correct. La valeur topLayout peut être utilisée par le contrôleur de vue de conteneur pour disposer les sous-vues de sa vue.
La référence dit:
Pour utiliser un guide de mise en page supérieur sans utiliser de contraintes, obtenez la position du guide par rapport à la limite supérieure de la vue contenant.
Dans le parent, par rapport à la vue conteneur, la valeur sera 64.
Dans l'enfant, par rapport à la vue conteneur (le parent), la valeur sera 0.
Dans le conteneur View Controller, vous pouvez utiliser la propriété de cette façon:
- (void) viewWillLayoutSubviews {
CGRect viewBounds = self.view.bounds;
CGFloat topBarOffset = self.topLayoutGuide.length;
for (UIView *view in [self.view subviews]){
view.frame = CGRectMake(viewBounds.Origin.x, viewBounds.Origin.y+topBarOffset, viewBounds.size.width, viewBounds.size.height-topBarOffset);
}
}
Le contrôleur de vue enfant n'a pas besoin de savoir qu'il existe une barre de navigation et d'état: son parent aura déjà présenté ses sous-vues en tenant compte de cela.
Si je crée un nouveau projet basé sur une page, l'incorpore dans un contrôleur de navigation et ajoute ce code aux contrôleurs de vue parents, il semble fonctionner correctement:
La documentation indique d'utiliser topLayoutGuide dans viewDidLayoutSubviews si vous utilisez une sous-classe UIViewController, ou layoutSubviews si vous utilisez une sous-classe UIView.
Si vous l'utilisez dans ces méthodes, vous devriez obtenir une valeur non nulle appropriée.
Lien de documentation: https://developer.Apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//Apple_ref/occ/instp/UIViewController/topLayoutGuide
Si vous avez UIPageViewController
comme OP et que vous avez par exemple des contrôleurs de vue de collection en tant qu'enfants. Il s'avère que la correction de l'encart de contenu est simple et fonctionne sur iOS 8:
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
UIEdgeInsets insets = self.collectionView.contentInset;
insets.top = self.parentViewController.topLayoutGuide.length;
self.collectionView.contentInset = insets;
self.collectionView.scrollIndicatorInsets = insets;
}
Cela a été résolu dans iOS 8.
Comment définir la position topLayoutGuide pour le contrôleur de vue enfant
Essentiellement, le contrôleur de vue de conteneur doit contraindre le contrôleur de vue enfant (top|bottom|left|right)LayoutGuide
comme pour tout autre point de vue. (Dans iOS 7, il était déjà entièrement contraint à une priorité requise, donc cela n'a pas fonctionné.)
Je pense que les guides sont certainement destinés à être définis pour les contrôleurs enfants imbriqués. Par exemple, supposons que vous ayez:
Cela aurait du sens: cela signifie que le contrôleur de vue imbriqué peut définir des contraintes pour empêcher les chevauchements indésirables, tout comme un niveau supérieur. Il n'a pas à se soucier qu'il est imbriqué, ni à quel endroit sur l'écran son parent l'affiche, et le contrôleur de vue parent n'a pas besoin de savoir comment l'enfant veut interagir avec la barre d'état.
Cela semble également être ce que dit la documentation - ou une partie de la documentation, au moins -:
Le guide de disposition supérieur indique la distance, en points, entre le haut de la vue d'un contrôleur de vue et le bas de la barre inférieure qui recouvre la vue
Cela ne dit rien sur le fait de travailler uniquement pour les contrôleurs de vue de niveau supérieur.
Mais, je ne sais pas si c'est ce qui se passe réellement. J'ai certainement vu des contrôleurs de vue enfant avec des topLayoutGuides différents de zéro, mais je suis toujours en train de comprendre les bizarreries. (Dans mon cas, le guide supérieur devrait être nul, car la vue n'est pas en haut de l'écran, ce qui est ce que je frappe tête contre en ce moment ...)
Je ne sais pas si quelqu'un a toujours eu un problème avec cela, comme je l'ai fait il y a quelques minutes.
Mon problème est comme ça (gif source de https://knuspermagier.de/2014-fixing-uipageviewcontrollers-top-layout-guide-problems.html =).
Pour faire court, ma pageViewController a 3 viewcontrollers enfants. Le premier viewcontroller est bien, mais quand je passe au suivant, la vue entière est incorrectement décalée vers le haut (~ 20 pixels, je suppose), mais reviendra à la normale après que mon doigt soit hors de l'écran.
Je suis resté éveillé toute la nuit à chercher une solution mais je n'ai toujours pas de chance d'en trouver une. Puis tout à coup, j'ai eu cette idée folle:
[pageViewController setViewControllers:@[listViewControllers[1]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished) {
}];
[pageViewController setViewControllers:@[listViewControllers[0]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished) {
}];
Ma listViewControllers a 3 viewcontrollers enfants. Celui à l'index 0 a un problème, donc je l'ai d'abord défini comme racine de pageviewcontroller, et juste après cela, je l'ai remis au premier contrôleur de vue (comme je m'y attendais). Voila, ça a marché!
J'espère que ça aide!
Il s'agit d'un comportement malheureux qui semble avoir été corrigé dans iOS 11 avec la refonte de l'API de la zone de sécurité. Cela dit, vous obtiendrez toujours la valeur correcte du contrôleur de vue racine. Par exemple, si vous souhaitez que la hauteur de la zone de sécurité supérieure soit antérieure à iOS 11:
Swift 4
let root = UIApplication.shared.keyWindow!.rootViewController!
let topLayoutGuideLength = root.topLayoutGuide.length
C'est l'approche pour la longueur de guide connue. Créez des contraintes non pas pour les guides, mais pour afficher le dessus avec des constantes fixes en supposant que la distance du guide sera.
Implémentation rapide de @NachoSoto:
extension UIViewController {
func navigationBarTopLayoutGuide() -> UILayoutSupport {
if let parentViewController = self.parentViewController {
if !parentViewController.isKindOfClass(UINavigationController) {
return parentViewController.navigationBarTopLayoutGuide()
}
}
return self.topLayoutGuide
}
func navigationBarBottomLayoutGuide() -> UILayoutSupport {
if let parentViewController = self.parentViewController {
if !parentViewController.isKindOfClass(UINavigationController) {
return parentViewController.navigationBarBottomLayoutGuide()
}
}
return self.bottomLayoutGuide
}
}