web-dev-qa-db-fra.com

topLayoutGuide dans le contrôleur de vue enfant

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)

69
hpique

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 :)

48
NachoSoto

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];
}
11
chliul

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:

enter image description here

10
vinaut

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

8
CornPuff

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;
}
3
highmaintenance

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é.)

2
Jesse Rusak

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:

  • Un écran 100x50, avec une barre d'état de 20 pixels en haut.
  • Un contrôleur de vue de haut niveau, couvrant toute la fenêtre. Son topLayoutGuide est de 20.
  • Un contrôleur de vue imbriqué à l'intérieur de la vue de dessus couvrant les 95 pixels inférieurs, par exemple. 5 pixels vers le bas depuis le haut de l'écran. Cette vue doit avoir un topLayoutGuide de 15, car ses 15 premiers pixels sont couverts par la barre d'état.

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

( https://developer.Apple.com/library/ios/documentation/UIKit/Reference/UILayoutSupport_Protocol/Reference/Reference.html )

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 ...)

1
Glenn Maynard

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!

0
Dat Kin

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
0
bsod

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.

0
kirander

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
    }
}
0
Vive