167 votes

AutoLayout avec UIViews cachés ?

J'ai l'impression que c'est un paradigme assez commun de montrer/cacher UIViews le plus souvent UILabels en fonction de la logique commerciale. Ma question est la suivante : quelle est la meilleure façon d'utiliser AutoLayout pour répondre aux vues cachées comme si leur cadre était 0x0. Voici un exemple d'une liste dynamique de 1 à 3 fonctionnalités.

Dynamic features list

Actuellement, il y a un espace de 10px entre le bouton et la dernière étiquette, qui ne peut évidemment pas glisser vers le haut lorsque l'étiquette est cachée. Pour l'instant, j'ai créé une sortie pour cette contrainte et je modifie la constante en fonction du nombre d'étiquettes que j'affiche. C'est évidemment un peu compliqué puisque j'utilise des valeurs constantes négatives pour faire glisser le bouton vers le haut au-dessus des cadres cachés. C'est également mauvais parce que la contrainte n'est pas liée à des éléments de mise en page réels, mais à des calculs statiques sournois basés sur des hauteurs/plages connues d'autres éléments, et que cela va à l'encontre de ce pour quoi AutoLayout a été conçu.

Je pourrais évidemment créer de nouvelles contraintes en fonction de mes étiquettes dynamiques, mais c'est beaucoup de microgestion et de verbosité pour essayer de réduire simplement quelques espaces blancs. Existe-t-il de meilleures approches ? Changer la taille du cadre 0,0 et laisser AutoLayout faire son travail sans manipuler les contraintes ? Supprimer complètement les vues ?

Honnêtement, la modification de la constante dans le contexte de la vue cachée ne nécessite qu'une seule ligne de code avec un calcul simple. Recréer de nouvelles contraintes avec constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: semble si lourd.

Edit Feb 2018 : Voir la réponse de Ben avec UIStackView s

0 votes

Merci Ryan pour cette question. Je devenais fou de savoir comment faire dans les cas que vous avez demandés. Chaque fois que je cherche le tutoriel pour autolayout, la plupart d'entre eux disent de se référer au site de tutoriel raywenderlich que je trouve un peu difficile à comprendre.

2voto

TMin Points 1328

Ma méthode préférée est très similaire à celle suggérée par Jorge Arimany.

Je préfère créer des contraintes multiples. Créez d'abord vos contraintes pour le moment où la deuxième étiquette est visible. Créez une sortie pour la contrainte entre le bouton et la deuxième étiquette (si vous utilisez objc, assurez-vous qu'elle est forte). Cette contrainte détermine la hauteur entre le bouton et la deuxième étiquette lorsqu'elle est visible.

Créez ensuite une autre contrainte qui spécifie la hauteur entre le bouton et l'étiquette supérieure lorsque le deuxième bouton est masqué. Créez une sortie vers la deuxième contrainte et assurez-vous que cette sortie a un pointeur fort. Ensuite, décochez la case installed dans le constructeur d'interface, et assurez-vous que la priorité de la première contrainte est inférieure à celle de la seconde.

Enfin, lorsque vous masquez la deuxième étiquette, basculez l'option .isActive de ces contraintes et appeler setNeedsDisplay()

Et c'est tout, pas de numéros magiques, pas de maths, si vous avez plusieurs contraintes à activer et à désactiver, vous pouvez même utiliser des collections de sorties pour les garder organisées par état. (AKA garder toutes les contraintes cachées dans une OutletCollection et les non cachées dans une autre et juste itérer sur chaque collection pour basculer leur statut .isActive).

Je sais que Ryan Romanchuk a dit qu'il ne voulait pas utiliser de contraintes multiples, mais j'ai l'impression que ce n'est pas du micromanagement et que c'est plus simple que de créer dynamiquement des vues et des contraintes par programme (ce qu'il voulait éviter si je lis bien la question).

J'ai créé un exemple simple, j'espère qu'il sera utile...

import UIKit

class ViewController: UIViewController {

    @IBOutlet var ToBeHiddenLabel: UILabel!

    @IBOutlet var hiddenConstraint: NSLayoutConstraint!
    @IBOutlet var notHiddenConstraint: NSLayoutConstraint!

    @IBAction func HideMiddleButton(_ sender: Any) {

        ToBeHiddenLabel.isHidden = !ToBeHiddenLabel.isHidden
        notHiddenConstraint.isActive = !notHiddenConstraint.isActive
        hiddenConstraint.isActive = !hiddenConstraint.isActive

        self.view.setNeedsDisplay()
    }
}

enter image description here

0voto

pallzoltan Points 126

Je vais aussi fournir ma solution, pour offrir de la variété). Je pense que créer une sortie pour la largeur/hauteur de chaque élément plus pour l'espacement est juste ridicule et fait exploser le code, les erreurs possibles et le nombre de complications.

Ma méthode supprime toutes les vues (dans mon cas, les instances UIImageView), sélectionne celles qui doivent être rajoutées, et dans une boucle, elle les rajoute toutes et crée de nouvelles contraintes. C'est en fait très simple, suivez bien. Voici mon code rapide et sale pour le faire :

// remove all views
[self.twitterImageView removeFromSuperview];
[self.localQuestionImageView removeFromSuperview];

// self.recipients always has to be present
NSMutableArray *items;
items = [@[self.recipients] mutableCopy];

// optionally add the twitter image
if (self.question.sharedOnTwitter.boolValue) {
    [items addObject:self.twitterImageView];
}

// optionally add the location image
if (self.question.isLocal) {
    [items addObject:self.localQuestionImageView];
}

UIView *previousItem;
UIView *currentItem;

previousItem = items[0];
[self.contentView addSubview:previousItem];

// now loop through, add the items and the constraints
for (int i = 1; i < items.count; i++) {
    previousItem = items[i - 1];
    currentItem = items[i];

    [self.contentView addSubview:currentItem];

    [currentItem mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(previousItem.mas_centerY);
        make.right.equalTo(previousItem.mas_left).offset(-5);
    }];
}

// here I just connect the left-most UILabel to the last UIView in the list, whichever that was
previousItem = items.lastObject;

[self.userName mas_remakeConstraints:^(MASConstraintMaker *make) {
    make.right.equalTo(previousItem.mas_left);
    make.leading.equalTo(self.text.mas_leading);
    make.centerY.equalTo(self.attachmentIndicator.mas_centerY);;
}];

J'obtiens une mise en page et un espacement propres et cohérents. Mon code utilise Masonry, je le recommande vivement : https://github.com/SnapKit/Masonry

0voto

Vladimir Points 4045

Essayez BoxView il rend la mise en page dynamique concise et lisible.
Dans votre cas, c'est le cas :

    boxView.optItems = [
        firstLabel.boxed.useIf(isFirstLabelShown),
        secondLabel.boxed.useIf(isSecondLabelShown),
        button.boxed
    ]

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