UIStackView "Impossible de satisfaire simultanément les contraintes" sur les vues masquées "écrasées"

95

Lorsque mes "lignes" UIStackView sont écrasées, elles lancent AutoLayout avertissements. Cependant, ils s'affichent bien et rien d'autre n'est faux à part ces types de journalisation:

Impossible de satisfaire simultanément les contraintes. Vous ne voulez probablement pas qu'au moins une des contraintes de la liste suivante. Essayez ceci: (1) regardez chaque contrainte et essayez de comprendre à laquelle vous ne vous attendez pas; (2) trouvez le code qui a ajouté la ou les contraintes indésirables et corrigez-le. (Remarque: si vous constatez NSAutoresizingMaskLayoutConstraintsque vous ne comprenez pas, reportez-vous à la documentation de la UIViewpropriété translatesAutoresizingMaskIntoConstraints) (

Donc, je ne sais pas encore comment résoudre ce problème, mais cela ne semble rien casser en plus d'être ennuyeux.

Quelqu'un sait-il comment le résoudre? Fait intéressant, les contraintes de mise en page sont assez souvent étiquetées avec «UISV-Hiding» , indiquant qu'il devrait peut-être ignorer les minimums de hauteur pour les sous-vues ou quelque chose dans ce cas?

Ben Guild
la source
1
Cela semble être résolu dans iOS11, ne recevant aucun avertissement ici
trappeur

Réponses:

204

Vous rencontrez ce problème car lorsque vous définissez une sous-vue de l'intérieur UIStackViewsur masquée, elle contraint d'abord sa hauteur à zéro afin de l'animer.

J'obtenais l'erreur suivante:

2015-10-01 11:45:13.732 <redacted>[64455:6368084] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5be508d0 V:|-(8)-[UISegmentedControl:0x7f7f5bec4180]   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5bdfbda0 'UISV-hiding' V:[UIView:0x7f7f5be69d30(0)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

Ce que j'essayais de faire, c'était de placer un UIViewdans mon UIStackViewqui contenait un UISegmentedControlencart de 8pts sur chaque bord.

Lorsque je le définissais sur masqué, il tentait de contraindre la vue du conteneur à une hauteur nulle, mais comme j'ai un ensemble de contraintes de haut en bas, il y avait un conflit.

Pour résoudre le problème, j'ai changé ma priorité de 8 points en haut et en bas des contraintes de 1000 à 999 afin que la UISV-hidingcontrainte puisse alors avoir la priorité si nécessaire.

liamnichols
la source
il convient de noter que si vous avez juste une contrainte de hauteur ou de largeur sur ces derniers et que vous réduisez leur priorité, cela ne fonctionnera pas. Vous devez supprimer la hauteur / largeur et ajouter les fonds de fuite supérieurs, puis définir leur priorité comme inférieure et cela fonctionne
bolnad
4
Changer les priorités a également fonctionné pour moi. Suppression également de toutes les contraintes en excès (grisées) qui ont été accidentellement copiées à partir des classes de taille inutilisées. CONSEIL IMPORTANT: pour déboguer plus facilement ces problèmes, définissez une chaîne IDENTIFER sur chaque contrainte. Ensuite, vous pouvez voir quelle contrainte était méchante dans le message de débogage.
Womble
3
Dans mon cas, je n'ai qu'à baisser la priorité sur la hauteur et ça marche.
pixelfreak
Cette astuce IDENTIFIER est géniale! Je me suis toujours demandé comment donner les contraintes dans les noms des messages de débogage, je cherchais toujours à ajouter quelque chose à la vue plutôt que la contrainte elle-même. Merci @Womble!
Ryan
Merci! La priorité de 1000 à 999 a fait l'affaire.Xcode: Version 8.3.3 (8E3004b)
Michael Garito
52

J'avais un problème similaire qui n'était pas facile à résoudre. Dans mon cas, j'avais une vue de pile intégrée dans une vue de pile. UIStackView interne avait deux étiquettes et un espacement non nul spécifié.

Lorsque vous appelez addArrangedSubview (), il crée automatiquement des contraintes similaires à celles-ci:

V:|[innerStackView]|              | = outerStackView

  V:|[label1]-(2)-[label2]|       | = innerStackView

Maintenant, lorsque vous essayez de masquer le innerStackView, vous obtenez un avertissement de contraintes ambiguës.

Pour comprendre pourquoi, voyons d'abord pourquoi cela ne se produit pas quand innerStackView.spacingest égal à 0. Lorsque vous appelez innerStackView.hidden = true, @liamentnichols avait raison ... outerStackViewinterceptera comme par magie cet appel et créera une contrainte de masquage UISV de0 hauteur avec la priorité 1000 (obligatoire). Il s'agit probablement de permettre aux éléments de la vue de la pile d'être animés hors de vue au cas où votre code de masquage serait appelé dans un bloc. Malheureusement, il ne semble pas y avoir de moyen d'empêcher l'ajout de cette contrainte. Néanmoins, vous n'obtiendrez pas un avertissement "Impossible de satisfaire simultanément les contraintes" (USSC), car ce qui suit se produit:UIView.animationWithDuration()

  1. La hauteur de label1 est définie sur 0
  2. l'espacement entre les deux étiquettes était déjà défini comme 0
  3. La hauteur de label2 est définie sur 0
  4. la hauteur de innerStackView est définie sur 0

Il est clair que ces 4 contraintes peuvent être satisfaites. La vue de la pile transforme simplement tout en un pixel de 0 hauteur.

Revenons maintenant à l'exemple de buggy, si nous définissons le spacingsur 2, nous avons maintenant ces contraintes:

  1. La hauteur de label1 est définie sur 0
  2. l'espacement entre les deux étiquettes a été automatiquement créé par la vue de pile avec une hauteur de 2 pixels avec une priorité de 1 000.
  3. La hauteur de label2 est définie sur 0
  4. la hauteur de innerStackView est définie sur 0

La vue de la pile ne peut pas être à la fois haute de 0 pixels et son contenu de 2 pixels de haut. Les contraintes ne peuvent pas être satisfaites.

Remarque: vous pouvez voir ce comportement avec un exemple plus simple. Ajoutez simplement un UIView à une vue de pile en tant que sous-vue organisée. Ensuite, définissez une contrainte de hauteur sur cet UIView avec une priorité de 1000. Maintenant, essayez d'appeler hide.

Remarque: pour une raison quelconque, cela ne s'est produit que lorsque ma vue de pile était une sous-vue d'un UICollectionViewCell ou UITableViewCell. Cependant, vous pouvez toujours reproduire ce comportement en dehors d'une cellule en appelantinnerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) la prochaine boucle d'exécution après avoir masqué la vue de la pile interne.

Remarque: même si vous essayez d'exécuter le code dans un UIView.performWithoutAnimations, la vue de la pile ajoutera toujours une contrainte de hauteur 0 qui provoquera l'avertissement USSC.


Il existe au moins 3 solutions à ce problème:

  1. Avant de masquer un élément dans une vue de pile, vérifiez s'il s'agit d'une vue de pile, et si c'est le cas, remplacez le spacingpar 0. C'est ennuyeux car vous devez inverser le processus (et vous souvenir de l'espacement d'origine) chaque fois que vous affichez à nouveau le contenu.
  2. Au lieu de masquer des éléments dans une vue de pile, appelez removeFromSuperview. C'est encore plus ennuyeux car lorsque vous inversez le processus, vous devez vous rappeler insérer l'élément supprimé. Vous pouvez optimiser en appelant uniquement removeArrangedSubview, puis en vous cachant, mais il reste encore beaucoup de comptabilité à faire.
  3. Enveloppez les vues de pile imbriquées (qui ont une valeur différente de zéro spacing) dans un UIView. Spécifiez au moins une contrainte comme priorité non requise (999 ou moins). C'est la meilleure solution puisque vous n'avez pas à faire de comptabilité. Dans mon exemple, j'ai créé des contraintes de haut, de début et de fin à 1000 entre la vue de pile et la vue de wrapper, puis j'ai créé une contrainte 999 du bas de la vue de pile vers la vue de wrapper. De cette façon, lorsque la vue de la pile externe crée une contrainte de hauteur nulle, la contrainte 999 est rompue et vous ne voyez pas l'avertissement USSC. (Remarque: Ceci est similaire à la solution de Si le contentView.translatesAutoResizingMaskToConstraints d'une sous-classe UICollectionViewCell doit être défini surfalse )

En résumé, les raisons pour lesquelles vous obtenez ce comportement sont:

  1. Apple crée automatiquement 1000 contraintes de priorité pour vous lorsque vous ajoutez des sous-vues gérées à une vue de pile.
  2. Apple crée automatiquement une contrainte de hauteur 0 pour vous lorsque vous masquez une sous-vue d'une vue de pile.

Si Apple (1) vous avait autorisé à spécifier la priorité des contraintes (en particulier les espaceurs), ou (2) vous avait autorisé à désactiver la contrainte de masquage automatique d' UISV , ce problème serait facilement résolu.

Sensé
la source
6
Merci pour l'explication super approfondie et utile. Cela semble cependant être un bogue du côté d'Apple avec des vues de pile. Fondamentalement, leur fonction de "masquage" est incompatible avec leur fonction "d'espacement". Des idées ont-elles résolu ce problème depuis, ou ont ajouté des fonctionnalités pour empêcher le piratage avec des vues supplémentaires? (Encore une fois, bonne répartition des solutions potentielles et ne vous entendez pas sur l'élégance du n ° 3)
Marchy
1
Est-ce que chaque UIStackViewenfant d'un UIStackViewbesoin d'être enveloppé dans un UIViewou juste celui que vous cherchez à cacher / dévoiler?
Adrian
1
Cela ressemble à un très mauvais oubli de la part d'Apple. Surtout parce que les avertissements et les erreurs entourant l'utilisation de UIStackViewont tendance à être cryptiques et difficiles à comprendre.
bompf
C'est une bouée de sauvetage. J'avais un problème où UIStackViews ajouté à un UITableViewCell causait du spam dans le journal des erreurs AutoLayout, chaque fois que la cellule était réutilisée. L'incorporation de stackView dans un UIView avec sa priorité de contrainte d'ancrage inférieure définie sur faible a résolu le problème. Cela amène le débogueur de vue à afficher les éléments de stackView comme ayant des hauteurs ambiguës, mais il s'affiche correctement dans l'application, sans spam du journal des erreurs. MERCI.
Womble
6

La plupart du temps, cette erreur peut être résolue en abaissant la priorité des contraintes afin d'éliminer les conflits.

Luciano Almeida
la source
Que voulez-vous dire? Les contraintes sont toutes relatives dans les vues qui sont empilées ....
Ben Guild
Je suis désolé, je ne comprends pas bien votre question, mon anglais l'est, mais ce que je pense, c'est que les contraintes sont relatives à la vue à laquelle elle est associée, si la vue devient masquée les contraintes sont devenues inactives. Je ne sais pas si c'est votre doute, mais j'espère pouvoir vous aider.
Luciano Almeida
Cela semble provenir du moment où le contenu est écrasé ou en cours d'affichage / caché. Dans ce cas, il devient partiellement visible. - Peut-être est-il nécessaire de passer à travers et d'éliminer les constantes verticales minimales car il peut être écrasé à 0 hauteur?
Ben Guild
2

Lorsque vous définissez une vue sur masqué, l ' UIStackviewessaiera de l'animer. Si vous voulez cet effet, vous devrez définir la bonne priorité pour les contraintes afin qu'elles ne soient pas en conflit (comme beaucoup l'ont suggéré ci-dessus).

Cependant, si vous ne vous souciez pas de l'animation (peut-être que vous la cachez dans ViewDidLoad), vous pouvez simplifier removeFromSuperviewce qui aura le même effet mais sans aucun problème avec les contraintes, car celles-ci seront supprimées avec la vue.

Oren
la source
1

Sur la base de la réponse de @ Senseful, voici une extension UIStackView pour envelopper une vue de pile dans une vue et appliquer les contraintes qu'il recommande:

/// wraps in a `UIView` to prevent autolayout warnings when a stack view with spacing is placed inside another stack view whose height might be zero (usually due to `hidden` being `true`).
/// See http://stackoverflow.com/questions/32428210
func wrapped() -> UIView {
    let wrapper = UIView()
    translatesAutoresizingMaskIntoConstraints = false
    wrapper.addSubview(self)

    for attribute in [NSLayoutAttribute.Top, .Left, .Right, .Bottom] {
        let constraint = NSLayoutConstraint(item: self,
                                            attribute: attribute,
                                            relatedBy: .Equal,
                                            toItem: wrapper,
                                            attribute: attribute,
                                            multiplier: 1,
                                            constant: 0)
        if attribute == .Bottom { constraint.priority = 999 }
        wrapper.addConstraint(constraint)
    }
    return wrapper
}

Au lieu d'ajouter votre stackView, utilisez stackView.wrapped().

Ben Packard
la source
1

Tout d'abord, comme d'autres l'ont suggéré, assurez-vous que les contraintes que vous pouvez contrôler, c'est-à-dire pas les contraintes inhérentes à UIStackView, sont définies sur la priorité 999 afin qu'elles puissent être remplacées lorsque la vue est masquée.

Si vous rencontrez toujours le problème, le problème est probablement dû à l'espacement dans les StackViews masquées. Ma solution était d'ajouter un UIView comme espaceur et de définir l'espacement UIStackView à zéro. Définissez ensuite les contraintes View.height ou View.width (en fonction d'une pile verticale ou horizontale) sur l'espacement de StackView.

Ajustez ensuite les priorités de respect du contenu et de résistance à la compression du contenu de vos vues nouvellement ajoutées. Vous devrez peut-être également modifier la distribution du StackView parent.

Tout ce qui précède peut être effectué dans Interface Builder. Vous devrez peut-être en outre masquer / afficher certaines des vues nouvellement ajoutées par programme afin de ne pas avoir d'espacement indésirable.

Peter Coyle
la source
1

J'ai récemment lutté contre des erreurs de mise en page automatique en masquant un fichier UIStackView. Plutôt que de faire un tas de piles de livres et d'emballage UIViews, j'ai choisi de créer un point de vente pour mes parentStackViewet des points de vente pour les enfants que je veux cacher / afficher.

@IBOutlet weak var parentStackView: UIStackView!
@IBOutlet var stackViewNumber1: UIStackView!
@IBOutlet var stackViewNumber2: UIStackView!

Dans le storyboard, voici à quoi ressemble mon parentStack:

entrez la description de l'image ici

Il a 4 enfants et chacun des enfants a un tas de vues de pile à l'intérieur d'eux. Lorsque vous masquez une vue de pile, si elle contient des éléments d'interface utilisateur qui sont également des vues de pile, vous verrez un flux d'erreurs de disposition automatique. Plutôt que de me cacher, j'ai choisi de les supprimer.

Dans mon exemple, parentStackViewscontient un tableau des 4 éléments: Top Stack View, StackViewNumber1, Stack View Number 2 et Stop Button. Leurs indices enarrangedSubviews sont 0, 1, 2 et 3, respectivement. Lorsque je veux en cacher un, je le supprime simplement du parentStackView's arrangedSubviewstableau. Comme il n'est pas faible, il reste en mémoire et vous pouvez simplement le remettre à l'index souhaité plus tard. Je ne le réinitialise pas, donc il se bloque jusqu'à ce qu'il soit nécessaire, mais ne gonfle pas la mémoire.

Donc, fondamentalement, vous pouvez ...

1) Faites glisser IBOutlets pour votre pile parent et les enfants que vous souhaitez masquer / afficher dans le storyboard.

2) Lorsque vous souhaitez les masquer, supprimez la pile que vous souhaitez masquer du parentStackView's arrangedSubviewstableau.

3) Appelez self.view.layoutIfNeeded()avec UIView.animateWithDuration.

Notez que les deux derniers stackViews ne le sont pas weak. Vous devez les conserver lorsque vous les affichez.

Disons que je veux masquer stackViewNumber2:

parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()

Puis animez-le:

UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

Si vous souhaitez "afficher" une version stackViewNumber2ultérieure, vous pouvez simplement l'insérer dans l' parentStackView arrangedSubViewsindex souhaité et animer la mise à jour.

parentStackView.removeArrangedSubview(stackViewNumber1)
stackViewNumber1.removeFromSuperview()
parentStackView.insertArrangedSubview(stackViewNumber2, at: 1)

// Then animate it
UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

J'ai trouvé que c'était beaucoup plus facile que de faire de la comptabilité sur les contraintes, de jouer avec les priorités, etc.

Si vous souhaitez masquer quelque chose par défaut, vous pouvez simplement le disposer sur le storyboard, le supprimer viewDidLoadet le mettre à jour sans utiliser l'animation view.layoutIfNeeded().

Adrian
la source
1

J'ai rencontré les mêmes erreurs avec les vues de pile intégrées, même si tout fonctionnait bien au moment de l'exécution.

J'ai résolu les erreurs de contrainte en masquant d'abord toutes les vues de la sous-pile (paramètre isHidden = true) avant de masquer la vue de la pile parente.

Faire cela n'a pas eu toute la complexité de la suppression des vues subordonnées, le maintien d'un index pour le moment où il fallait les rajouter.

J'espère que cela t'aides.

Will Stevens
la source
1

Senseful a fourni une excellente réponse à la racine du problème ci-dessus, je vais donc aller directement à la solution.

Tout ce que vous avez à faire est de définir la priorité de toutes les contraintes stackView à une valeur inférieure à 1000 (999 feront le travail). Par exemple, si le stackView est contraint à gauche, à droite, en haut et en bas de sa vue de supervision, les 4 contraintes doivent avoir la priorité inférieure à 1000.

Linh Ta
la source
0

Vous avez peut-être créé une contrainte en travaillant avec une certaine classe de taille (ex: wCompact hRegular), puis vous avez créé un doublon lorsque vous êtes passé à une autre classe de taille (ex: wAny hAny). vérifiez les contraintes des objets UI dans différentes classes de taille et voyez s'il y a des anomalies avec les contraintes. vous devriez voir les lignes rouges indiquant les contraintes de collision. Je ne peux pas mettre de photo avant d'avoir 10 points de réputation désolé: /

FM
la source
Ah d'accord. mais j'ai eu cette erreur quand j'ai eu le cas que j'ai décrit drive.google.com/file/d/0B-mn7bZcNqJMVkt0OXVLVVdnNTA/…
FM
Oui, je ne vois absolument aucun rouge en basculant les classes de taille dans le constructeur d'interface. Je n'ai utilisé que la taille "Any".
Ben Guild
0

Je voulais cacher tout UIStackView à la fois mais j'obtenais les mêmes erreurs que l'OP, cela a résolu le problème pour moi:

for(UIView *currentView in self.arrangedSubviews){
    for(NSLayoutConstraint *currentConstraint in currentView.constraints){
        [currentConstraint setPriority:999];
    }
}
sp00ky
la source
Cela n'a pas fonctionné pour moi car le moteur de mise en page automatique se plaint lorsque vous modifiez une contrainte requise ( priority = 1000) en non requise ( priority <= 999).
Senseful
0

J'avais une rangée de boutons avec une contrainte de hauteur. Cela se produit lorsqu'un bouton est masqué. La définition de la priorité de la contrainte de hauteur des boutons sur 999 a résolu le problème.

Niklas
la source
-2

Cette erreur n'a rien à voir avec UIStackView. Cela se produit lorsque vous avez des contraintes de conflit avec les mêmes priorités. Par exemple, si vous avez une contrainte indique que la largeur de votre vue est de 100, et que vous avez une autre contrainte en même temps, indique que la largeur de la vue est de 25% de son conteneur. Il y a manifestement deux contraintes contradictoires. La solution est de supprimer l'un d'entre eux.

William Kinaan
la source
-3

NOP avec [mySubView removeFromSuperview]. J'espère que cela pourrait aider quelqu'un :)

Grégoire GUYON
la source
Désolé, qu'est-ce que NOP?
Pang