[Ios] AutoLayout avec des UIViews cachés?


Answers

La solution consistant à utiliser une constante 0 cachée et une autre constante si vous l'affichez à nouveau est fonctionnelle, mais elle n'est pas satisfaisante si votre contenu a une taille flexible. Vous devez mesurer votre contenu flexible et définir une constante. Cela est incorrect et présente des problèmes si le contenu change de taille en raison d'événements liés au serveur ou à l'interface utilisateur.

J'ai une meilleure solution.

L'idée est de définir la règle de hauteur a 0 pour avoir une priorité élevée lorsque nous cachons l'élément afin qu'il n'occupe aucun espace autolayout.

Voici comment vous faites cela:

1. Définissez une largeur (ou hauteur) de 0 dans le constructeur d'interface avec une priorité basse.

Interface Builder ne criera pas sur les conflits car la priorité est faible. Testez le comportement en hauteur en définissant la priorité à 999 temporairement (1000 est interdit de muter par programmation, donc nous ne l'utiliserons pas). Le constructeur d'interface va probablement maintenant crier sur les contraintes conflictuelles. Vous pouvez les corriger en définissant des priorités sur les objets associés à 900 ou plus.

2. Ajoutez un point de vente pour pouvoir modifier la priorité de la contrainte de largeur dans le code:

3. Ajustez la priorité lorsque vous cachez votre élément:

cell.alertTimingView.hidden    = place.closingSoon != true
cell.alertTimingWidth.priority = place.closingSoon == true ? 250 : 999
Question

J'ai l'impression que c'est un paradigme assez courant pour montrer / cacher des UIViews , le plus souvent des UILabels , en fonction de la logique métier. Ma question est, 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 de liste dynamique de 1-3 fonctionnalités.

En ce moment j'ai un espace supérieur de 10px du bouton à la dernière étiquette, qui évidemment ne glissera pas vers le haut quand l'étiquette est cachée. En ce moment j'ai créé un débouché à cette contrainte et j'ai modifié la constante en fonction du nombre d'étiquettes affichées. C'est évidemment un peu hacky car j'utilise des valeurs constantes négatives pour pousser le bouton sur les cadres cachés. C'est aussi mauvais parce qu'il n'est pas contraint à des éléments de mise en page réels, juste des calculs statiques basés sur des hauteurs / rembourrages connus d'autres éléments, et de toute évidence luttant contre ce que l'AutoLayout a été construit.

Je pourrais évidemment créer de nouvelles contraintes en fonction de mes étiquettes dynamiques, mais c'est beaucoup de microgestion et beaucoup de verbosité pour essayer de simplement réduire certains espaces. Y a-t-il de meilleures approches? Changer la taille de l'image 0,0 et laisser AutoLayout faire son truc sans manipulation de contraintes? Suppression des vues complètement?

Honnêtement, il suffit de modifier la constante du contexte de la vue cachée pour obtenir 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




Je viens de découvrir que pour obtenir un UILabel pour ne pas prendre de l'espace, vous devez le cacher ET mettre son texte à une chaîne vide. (iOS 9)

Connaître ce fait / bug pourrait aider certaines personnes à simplifier leur mise en page, peut-être même celle de la question originale, alors j'ai pensé que je l'afficherais.




Il y a beaucoup de solutions ici mais mon approche habituelle est différente encore :)

Mettre en place deux ensembles de contraintes similaires à la réponse de Jorge Arimany et TMin:

Les trois contraintes marquées ont la même valeur pour la constante. Les contraintes notées A1 et A2 ont leur priorité définie sur 500, tandis que la contrainte marquée B a sa priorité définie sur 250 (ou UILayoutProperty.defaultLow dans le code).

Accrocher la contrainte B à un IBOutlet. Ensuite, lorsque vous masquez l'élément, vous devez simplement définir la priorité de la contrainte sur high (750):

constraintB.priority = .defaultHigh

Mais lorsque l'élément est visible, redéfinissez la priorité sur faible (250):

constraintB.priority = .defaultLow

L'avantage (certes mineur) de cette approche par rapport à la simple modification de la contrainte est que vous avez encore une contrainte de travail si l'élément transitoire est supprimé de la vue par d'autres moyens.




Dans ce cas, je mappe la hauteur de l'étiquette auteur à un IBOutlet approprié:

@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;

et quand je place la hauteur de la contrainte à 0.0f, nous conservons le "padding", parce que la hauteur du bouton Play le permet.

cell.authorLabelHeight.constant = 0; //Hide 
cell.authorLabelHeight.constant = 44; //Show




Je suis surpris qu'il n'y ait pas une approche plus élégante fournie par UIKit pour ce comportement désiré. Cela semble être une chose très commune à vouloir être capable de faire.

Depuis que IBOutlets connecté des contraintes à IBOutlets et que leurs constantes sont 0 , j'ai décidé de créer une extension qui donne une approche simple et dynamique pour masquer / afficher un UIView avec des contraintes Auto Layout.

Il masque simplement la vue et supprime les contraintes extérieures. Lorsque vous affichez à nouveau la vue, elle ajoute les contraintes. La seule mise en garde est que vous devrez spécifier des contraintes de basculement flexibles aux vues environnantes.

Modifier Cette réponse est destinée à iOS 8.4 et versions ultérieures. Dans iOS 9, utilisez simplement l'approche UIStackView .




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

Je préfère créer plusieurs contraintes. Commencez par créer vos contraintes lorsque la seconde étiquette est visible. Créer un exutoire pour la contrainte entre le bouton et la 2ème étiquette (si vous utilisez objc assurez-vous que c'est fort). Cette contrainte détermine la hauteur entre le bouton et la seconde étiquette lorsqu'elle est visible.

Puis créez une autre contrainte qui spécifie la hauteur entre le bouton et l'étiquette supérieure lorsque le second bouton est masqué. Créer une sortie à la deuxième contrainte et assurez-vous que cette sortie a un pointeur fort. Désactivez ensuite la case à cocher installed dans le constructeur d'interface et assurez-vous que la priorité de la première contrainte est inférieure à cette deuxième priorité de contraintes.

Enfin, lorsque vous cachez la seconde étiquette, basculez la propriété .isActive de ces contraintes et appelez setNeedsDisplay()

Et c'est tout, pas de nombres magiques, pas de maths, si vous avez plusieurs contraintes à activer et désactiver, vous pouvez même utiliser des collections de sortie pour les organiser par état. (AKA conserve toutes les contraintes cachées dans un OutletCollection et les contraintes non cachées dans un 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 plusieurs contraintes, mais je pense que ce n'est pas micromanage-y, et il est plus simple de créer dynamiquement des vues et des contraintes par programme (ce que je pense qu'il voulait éviter si je suis en train de lire la question à droite).

J'ai créé un exemple simple, j'espère que c'est 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()
    }
}