71 votes

Comment faire en sorte qu'une vue personnalisée se redimensionne avec la fenêtre grâce à Cocoa Auto Layout ?

J'ai une seule fenêtre avec une seule vue personnalisée dedans, et je veux que la vue personnalisée redimensionne avec la fenêtre de sorte qu'elle la remplisse entièrement à tout moment. Si j'écris :

NSView *contentView = [self.window contentView];
CustomView *customView = [[CustomView alloc] initWithFrame:[contentView bounds]];
[contentView addSubview:customView];
[contentView addConstraint:
    [NSLayoutConstraint constraintWithItem:customView
        attribute:NSLayoutAttributeWidth
        relatedBy:NSLayoutRelationEqual
        toItem:contentView
        attribute:NSLayoutAttributeWidth
        multiplier:1
        constant:0]];
[contentView addConstraint:
    [NSLayoutConstraint constraintWithItem:customView
        attribute:NSLayoutAttributeHeight
        relatedBy:NSLayoutRelationEqual
        toItem:contentView
        attribute:NSLayoutAttributeHeight
        multiplier:1
        constant:0]];

Alors la fenêtre ne me permet pas de la redimensionner.
Si j'ajoute:

[customView setTranslatesAutoresizingMaskIntoConstraints:NO];

Alors la vue personnalisée n'apparaît pas (drawRect: semble ne jamais être appelé). J'ai essayé différentes façons (y compris le format visuel @"|[customview]|") mais je rencontre toujours les mêmes problèmes. Je sais que cela pouvait être fait avec l'ancien système de redimensionnement automatique, avec :

[customView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];

mais je veux utiliser le système de disposition automatique Cocoa, et je veux l'utiliser pour des cas plus compliqués (comme plusieurs vues personnalisées qui remplissent toujours la fenêtre).

Est-ce que quelqu'un sait ce qui ne va pas et comment je devrais utiliser le système de disposition automatique pour obtenir le résultat que je veux?

107voto

Bavarious Points 43993

Avec Auto Layout, il existe au moins trois façons possibles de contraindre une vue pour qu'elle occupe l'ensemble de la vue de contenu de la fenêtre, en redimensionnant au besoin.

Contraintes de format visuel par rapport à la super vue

NSView *contentView = [_window contentView];
MyView *customView = [[MyView alloc] initWithFrame:[contentView bounds]];
[customView setTranslatesAutoresizingMaskIntoConstraints:NO];

[contentView addSubview:customView];

NSDictionary *views = NSDictionaryOfVariableBindings(customView);

[contentView addConstraints:
    [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[customView]|"
        options:0
        metrics:nil
        views:views]];

[contentView addConstraints:
    [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[customView]|"
    options:0
    metrics:nil
    views:views]];

Contraintes programmatiques pour les bords

(ceci devrait être équivalent au format visuel ci-dessus)

+ (void)addEdgeConstraint:(NSLayoutAttribute)edge superview:(NSView *)superview subview:(NSView *)subview {
    [superview addConstraint:[NSLayoutConstraint constraintWithItem:subview
        attribute:edge
        relatedBy:NSLayoutRelationEqual
        toItem:superview
        attribute:edge
        multiplier:1
        constant:0]];
}

et

NSView *contentView = [_window contentView];
MyView *customView = [[MyView alloc] initWithFrame:[contentView bounds]];
[customView setTranslatesAutoresizingMaskIntoConstraints:NO];

[contentView addSubview:customView];

[[self class] addEdgeConstraint:NSLayoutAttributeLeft superview:contentView subview:customView];
[[self class] addEdgeConstraint:NSLayoutAttributeRight superview:contentView subview:customView];
[[self class] addEdgeConstraint:NSLayoutAttributeTop superview:contentView subview:customView];
[[self class] addEdgeConstraint:NSLayoutAttributeBottom superview:contentView subview:customView];

Contrainte programmatique pour la taille

NSView *contentView = [_window contentView];
MyView *customView = [[MyView alloc] initWithFrame:[contentView bounds]];
[customView setTranslatesAutoresizingMaskIntoConstraints:NO];

[contentView addSubview:customView];

[contentView addConstraint:
    [NSLayoutConstraint constraintWithItem:customView
        attribute:NSLayoutAttributeWidth
        relatedBy:NSLayoutRelationEqual
        toItem:contentView
        attribute:NSLayoutAttributeWidth
        multiplier:1
        constant:0]];
[contentView addConstraint:
    [NSLayoutConstraint constraintWithItem:customView
        attribute:NSLayoutAttributeHeight
        relatedBy:NSLayoutRelationEqual
        toItem:contentView
        attribute:NSLayoutAttributeHeight
        multiplier:1
        constant:0]];

La troisième approche est celle mentionnée dans la question et cela peut ne pas fonctionner s'il existe d'autres contraintes. Par exemple, sans :

[customView setTranslatesAutoresizingMaskIntoConstraints:NO];

le masque d'auto-redimensionnement d'origine est également appliqué, ce qui entraîne le comportement décrit dans la question : la fenêtre n'est pas redimensionnée.

Comme mentionné par Regexident, vous pouvez utiliser :

[_window visualizeConstraints:[contentView constraints]];

pour déboguer Auto Layout. Il vaut également la peine de vérifier la sortie dans la console.

47voto

Ken Points 8074

@La réponse de Bavarious est bonne, je vais simplement ajouter quelques choses de plus.

Il est vraiment important d'apprendre à utiliser le support de débogage intégré! Comme pour beaucoup de développement, il n'est pas réaliste d'espérer tout faire correctement du premier coup. C'était une préoccupation majeure avec la mise en page automatique, donc nous avons mis beaucoup d'efforts dans le débogage. Étapes, en bref:

  1. Déterminer une vue qui est mal placée. Appeler la méthode -[NSView _subtreeDescription] à partir de gdb et/ou passer les arguments -NSShowAllViews YES peut aider à identifier quelle vue est mal.
  2. Déterminer la ou les contraintes qui sont incorrectes ou manquantes. -[NSLayoutConstraint constraintsAffectingLayoutForOrientation:] aide à vous donner un ensemble plus restreint de contraintes sur lesquelles travailler. -[NSWindow visualizeConstraints:] peut vous aider à voir quelles sont ces contraintes et vous pouvez voir à partir de cela laquelle de celles-ci n'est pas ce que vous voulez. Il montrera également si votre mise en page est ambiguë (pas assez de contraintes).
  3. Déterminer d'où vient la mauvaise contrainte. Le modèle de mise en page Cocoa dans Instruments est un peu comme l'instrument Leaks - il vous montrera tous les événements dans le cycle de vie d'une contrainte, comme où elle a été créée, ajoutée à une fenêtre, modifiée, etc. Donc une fois que vous savez quelle contrainte pose problème, utilisez le champ de recherche dans Instruments pour filtrer pour ne voir que cette contrainte, et vous pouvez voir les traces de pile pour tous les événements de cycle de vie et comprendre où vous avez fait quelque chose que vous ne vouliez pas.

Généralement, le genre de question que vous posteriez (mes trucs ne fonctionnent pas!) ne sera pas suffisant pour que les gens sachent ce qui ne va pas, c'est pourquoi il est important d'utiliser les outils de débogage. Regardez la vidéo de la session WWDC 2011 (gratuite pour tous) et la documentation pour en savoir plus à ce sujet.

Maiiiis je peux en fait dire ce qui s'est mal passé cette fois. :-) Avant que vous n'ayez désactivé translatesAutoresizingMaskIntoConstraints, vous étiez plus contraint que vous ne le vouliez - la largeur et la hauteur de votre vue étaient fixes, c'est pourquoi la fenêtre ne pouvait pas être redimensionnée. CEPENDANT, une fois que vous l'avez désactivé, vous aviez une mise en page ambiguë, car vous n'aviez pas attaché votre vue à quoi que ce soit! Vous aviez dit quelle devait être sa taille (la même que la super vue), mais pas elle devait être.

Ken

Cadres Cocoa, auteur principal de la mise en page automatique

5voto

larva Points 3773

Vous pouvez créer des contraintes avec Layout Anchors avec un format très facile à lire :

Code Swift2

func fit(childView: UIView, parentView: UIView) {
    childView.translatesAutoresizingMaskIntoConstraints = false
    childView.topAnchor.constraintEqualToAnchor(parentView.topAnchor).active = true
    childView.leadingAnchor.constraintEqualToAnchor(parentView.leadingAnchor).active = true
    childView.trailingAnchor.constraintEqualToAnchor(parentView.trailingAnchor).active = true
    childView.bottomAnchor.constraintEqualToAnchor(parentView.bottomAnchor).active = true
}

Utilisation :

parrentView.addSubview(childView)
fit(childView, parentView: parrentView)

0voto

Carmin Politano Points 19

J'ai découvert que -drawRect: ne sera pas appelé si le rectangle du cadre est 0,0,0,0. Des contraintes indésirables semblent causer le cadre à devenir 0,0,0,0.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X