web-dev-qa-db-fra.com

Comment redimensionner une vue personnalisée avec la fenêtre avec Cocoa Auto Layout?

J'ai une seule fenêtre avec une seule vue personnalisée et je veux que la vue personnalisée soit redimensionnée avec la fenêtre afin 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]];

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

[customView setTranslatesAutoresizingMaskIntoConstraints:NO];

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

[customView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];

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

Quelqu'un sait-il ce qui ne va pas et comment utiliser le système de disposition automatique pour obtenir le résultat souhaité?

71
Guillaume

Avec la disposition automatique, il existe (au moins) trois façons possibles de contraindre une vue afin qu'elle occupe la vue de contenu de la fenêtre entière, en la redimensionnant le cas échéant.

Contraintes de format visuel en ce qui concerne la superview

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

(cela 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 indiquée dans la question et elle peut ne pas fonctionner s'il y a d'autres contraintes. Par exemple, sans:

[customView setTranslatesAutoresizingMaskIntoConstraints:NO];

le masque de redimensionnement automatique 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 la disposition automatique. Cela vaut également la peine de vérifier la sortie de la console.

107
user557219

@ La réponse de Bavarious est bonne, je vais juste ajouter quelques éléments supplémentaires.

Il est vraiment important d'apprendre à utiliser le support de débogage intégré! Comme avec beaucoup de développement, il n'est pas réaliste d'espérer que vous obtiendrez toujours tout ce qu'il faut sur le premier coup. C'était une préoccupation majeure avec la disposition automatique, nous avons donc mis beaucoup d'efforts dans le débogage. Étapes, brièvement:

  1. Déterminez une vue au mauvais endroit. L'appel de la méthode - [NSView _subtreeDescription] à partir de gdb et/ou la transmission des arguments -NSShowAllViews YES peut aider à identifier quelle vue est incorrecte.
  2. Déterminez la ou les contraintes incorrectes ou manquantes. - [NSLayoutConstraint contraintesAffectingLayoutForOrientation:] vous permet de disposer d'un ensemble de contraintes plus restreint sur lequel travailler. - [NSWindow visualizeConstraints:] peut vous aider à voir quelles sont ces contraintes sont et vous pouvez voir de celles qui ne sont pas quelque chose que vous voulez être là. Il vous montrera également si votre mise en page est ambiguë (pas assez de contraintes).
  3. Déterminez d'où vient la mauvaise contrainte. Le modèle Cocoa Layout dans Instruments est un peu comme l'instrument Leaks - il vous montrera tous les événements du cycle de vie d'une contrainte, comme l'endroit où elle a été créée, ajoutée à une fenêtre, modifiée, etc. Donc, une fois que vous savez quoi la contrainte est le problème, utilisez le champ de recherche dans Instruments pour filtrer jusqu'à afficher uniquement cette contrainte, et vous pouvez voir les traces de tous les événements du cycle de vie et déterminer où vous avez fait quelque chose que vous ne vouliez pas.

Habituellement, le type de question que vous publiez (mes trucs ne fonctionnent pas!) Ne sera pas suffisant pour que les gens disent ce qui ne va pas, c'est l'une des raisons pour lesquelles il est important d'utiliser les trucs de débogage. Voir la vidéo de la session WWDC 2011 (gratuite pour tous) et les documents pour en savoir plus.

Buuuuut, je peux dire ce qui s'est mal passé cette fois. :-) Avant de désactiver translatesAutoresizingMaskIntoConstraints, vous étiez plus contraint que vous ne le souhaitiez - la largeur et la hauteur de votre vue étaient également fixes, c'est pourquoi la fenêtre ne pouvait pas être redimensionnée. APRÈS l'avoir désactivé, vous aviez une disposition ambiguë, car vous n'aviez fixé votre vue sur rien! Vous aviez dit à quel point il devait être grand (identique à la vue d'ensemble), mais pas il était censé être.

Ken

Cocoa Frameworks, auteur principal de la mise en page automatique

47
Ken

Vous pouvez créer des contraintes avec des ancres de mise en page au format très facile à lire:

code Swift 2

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)
5
larva

J'ai découvert que -drawRect: ne sera pas appelé dans le cas où le rectangle de trame est 0,0,0,0. Des contraintes indésirables semblent faire en sorte que le cadre devienne 0,0,0,0.

0
Carmin Politano