Qu'est-ce que NSLayoutConstraint «UIView-Encapsulated-Layout-Height» et comment dois-je procéder pour le forcer à recalculer proprement?

262

J'ai un UITableViewfonctionnement sous iOS 8 et j'utilise des hauteurs de cellule automatiques à partir de contraintes dans un storyboard.

Une de mes cellules contient une seule UITextViewet j'en ai besoin pour se contracter et se développer en fonction de la saisie de l'utilisateur - appuyez sur pour réduire / développer le texte.

Je fais cela en ajoutant une contrainte d'exécution à la vue texte et en modifiant la constante de la contrainte en réponse aux événements utilisateur:

-(void)collapse:(BOOL)collapse; {

    _collapsed = collapse;

    if(collapse)
        [_collapsedtextHeightConstraint setConstant: kCollapsedHeight]; // 70.0
    else
        [_collapsedtextHeightConstraint setConstant: [self idealCellHeightToShowFullText]];

    [self setNeedsUpdateConstraints];

}

Chaque fois que je fais cela, je l'enveloppe dans les tableViewmises à jour et appelle [tableView setNeedsUpdateConstraints]:

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

[tableView setNeedsUpdateConstraints];
// I have also tried 
// [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
// with exactly the same results.

[tableView endUpdates];

Lorsque je fais cela, ma cellule se développe (et s'anime en le faisant) mais j'obtiens un avertissement de contraintes:

2014-07-31 13:29:51.792 OneFlatEarth[5505:730175] 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:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>",

    "<NSLayoutConstraint:0x7f94dced2260 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']-(15)-|   (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",

    "<NSLayoutConstraint:0x7f94dced2350 V:|-(6)-[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']   (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",

    "<NSLayoutConstraint:0x7f94dced6480 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f94de5773a0(91)]>"
 )

Will attempt to recover by breaking constraint 

<NSLayoutConstraint:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>

388 est ma hauteur calculée, les autres contraintes sur la UITextViewmienne sont de Xcode / IB.

Le dernier me dérange - je suppose que UIView-Encapsulated-Layout-Height c'est la hauteur calculée de la cellule lors de son premier rendu - (j'ai défini ma UITextViewhauteur sur> = 70,0) mais il ne semble pas juste que cette contrainte dérivée annule ensuite une utilisateur mis à jour cnstraint.

Pire encore, bien que le code de mise en page dise qu'il essaie de briser ma contrainte de hauteur, il ne le fait pas - il continue à recalculer la hauteur de la cellule et tout dessine comme je le voudrais.

Alors, quelle est NSLayoutConstraint UIView-Encapsulated-Layout-Height(je suppose que c'est la hauteur calculée pour le dimensionnement automatique des cellules) et comment devrais-je le forcer à recalculer proprement?

Rog
la source
3
croix publiée sur les forums de développement Apple: devforums.apple.com/thread/238803
Rog
3
J'ai résolu un problème similaire de la manière suivante et cela fonctionne sur iOS 7/8. 1) Abaissez l'une des priorités de contrainte à 750. J'essaierais le 1er ou le 2e 2) Dans la sous-classe de cellule dans l'ensemble awakeFromNib self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;. Je pense que la définition initiale du masque de redimensionnement automatique empêche la dernière contrainte d'être ajoutée. J'ai trouvé cette solution ici: github.com/wordpress-mobile/WordPress-iOS/commit/…
Jesse
3
@RogerNolan, des nouvelles? J'ai trouvé le même problème en jouant avec la mise en page automatique dans Interface Builder. Certaines cellules provoquent ce problème, d'autres non.
orkenstein
3
Je ne pense pas que l'ajout d'une marque de redimensionnement automatique soit une bonne solution.
Rog
1
@RogerNolan générez-vous cette cellule dans IB avant d'effectuer ces changements de disposition? J'ai débogué le même problème mais je n'ajoutais aucune contrainte supplémentaire. J'ai réussi à supprimer l'avertissement en reconstruisant ma vue à partir de zéro, et lorsque j'ai diffé les 2 fichiers de storyboard, la seule différence était que la version avec les avertissements manquait la ligne <rect key="frame" x="0.0" y="0.0" width="600" height="110"/>dans sa définition, ce qui me fait croire qu'il s'agit d'un bogue IB . Au moins, le mien l'était de toute façon.
Ell Neal

Réponses:

301

Essayez de réduire la priorité de votre _collapsedtextHeightConstraintà 999. De cette façon, la UIView-Encapsulated-Layout-Heightcontrainte fournie par le système est toujours prioritaire.

Il est basé sur ce que vous retournez -tableView:heightForRowAtIndexPath:. Assurez-vous de renvoyer la bonne valeur et votre propre contrainte et celle générée doit être la même. La priorité inférieure pour votre propre contrainte n'est nécessaire que temporairement pour éviter les conflits lorsque les animations de réduction / expansion sont en cours.

Ortwin Gentz
la source
74
Ce serait exactement le contraire de ce que je veux. L'UIView-Encapsulated-Layout-Height est incorrect - il appartient à la disposition précédente.
Rog
7
La UIView-Encapsulated-Layout-Heightcontrainte est ajoutée par UITableView une fois les hauteurs déterminées. Je calcule la hauteur en fonction systemLayoutSizeFittingSizede la contentView. Ici, UIView-Encapsulated-Layout-Heightça n'a pas d'importance. Ensuite, la tableView définit explicitement contentSize à la valeur renvoyée par heightForRowAtIndexPath:. Dans ce cas, il est correct de réduire la priorité de nos contraintes personnalisées car la contrainte tableView doit avoir priorité après le calcul des rowHeights.
Ortwin Gentz
8
@OrtwinGentz: Nous ne comprenons toujours pas qu'il est correct d'abaisser la priorité de nos contraintes personnalisées car la contrainte tableView doit avoir la priorité après le calcul des rowHeights . Le truc, c'est que UIView-Encapsulated-Layout-Heightça ne va pas si je ne baisse pas la priorité ...
test
38
Je maintiens que cela évite les conflits et ne résout pas le problème réel - bien que cela ressemble toujours à un bogue Apple, je pense que c'est probablement la bonne chose à faire. Surtout à la lumière du fait qu'Apple recalcule cette contrainte et que tout se présente correctement après l'impression de l'erreur.
Rog
9
-1 pour cette réponse car elle suppose que la contrainte ajoutée est correcte. L'ajout UIView-Encapsulated-Layout-Widthdans mon cas est tout simplement faux, mais il semble être préféré à mes contraintes explicites lors de l'exécution.
ray
68

J'ai un scénario similaire: une vue de table avec une cellule de ligne, dans laquelle il y a quelques lignes d'objets UILabel. J'utilise iOS 8 et la mise en page automatique.

Lorsque j'ai pivoté, j'ai obtenu la mauvaise hauteur de ligne calculée par le système (43,5 est beaucoup moins que la hauteur réelle). On dirait:

"<NSLayoutConstraint:0x7bc2b2c0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7bc37f30(43.5)]>"

Ce n'est pas seulement un avertissement. La disposition de ma cellule de vue de table est terrible - tout le texte se chevauchait sur une seule ligne de texte.

Cela m'étonne que la ligne suivante "corrige" mon problème comme par magie (la mise en page automatique ne se plaint de rien et j'obtiens ce que j'attends à l'écran):

myTableView.estimatedRowHeight = 2.0; // any number but 2.0 is the smallest one that works

avec ou sans cette ligne:

myTableView.rowHeight = UITableViewAutomaticDimension; // by itself this line only doesn't help fix my specific problem
Pouce d'or
la source
8
Finalement! Ceci est une bonne réponse. Dans la session WWDC, il a été mentionné que si vous utilisez le dimensionnement automatique de la hauteur de ligne, vous devez définir une estimation de la hauteur de ligne ou de mauvaises choses se produisent. (Oui, Apple a vraiment fait des choses désagréables)
Abdalrahman Shatou
50
FWIW, l'ajout de l'estimation n'a fait aucune différence pour moi.
Benjohn
3
Il h. Je suis de retour à cette même réponse à nouveau, et je suis allé la mettre en œuvre avec joie et espoir. Encore une fois, cela ne fait aucune différence pour moi :-)
Benjohn
3
Estimations renvoyées par tableView: estimationHeightForRowAtIndexPath: DOIT être au moins aussi grande que la cellule. Sinon, la hauteur calculée de la table sera inférieure à la hauteur réelle et la table peut défiler vers le haut (par exemple, après le retour de la séquence de déroulement à la table). UITableViewAutomaticDimension ne doit pas être utilisé.
Matt
1
Notez que plus le estimatedRowHeightplus bas cellForRowAtIndexPathsera appelé le plus souvent au départ. la hauteur de la vue de table divisée par les temps estimés de RowHeight, pour être exact. Sur un iPad Pro 12 pouces, cela peut potentiellement être un nombre par milliers et marteler la source de données et entraîner un retard important.
Mojo66
32

J'ai pu faire disparaître l'avertissement en spécifiant une priorité sur l'une des valeurs de la contrainte que les messages d'avertissement indiquent qu'il devait casser (ci-dessous "Will attempt to recover by breaking constraint"). Il semble que tant que je fixe la priorité à quelque chose de supérieur à 49, l'avertissement disparaît.

Pour moi, cela signifiait changer ma contrainte, l'avertissement disait qu'il tentait de briser:

@"V:|[contentLabel]-[quoteeLabel]|"

à:

@"V:|-0@500-[contentLabel]-[quoteeLabel]|"

En fait, je peux ajouter une priorité à n'importe lequel des éléments de cette contrainte et cela fonctionnera. Peu importe lequel. Mes cellules finissent à la bonne hauteur et l'avertissement ne s'affiche pas. Roger, pour votre exemple, essayez d'ajouter @500juste après la 388contrainte de valeur de hauteur (par exemple 388@500).

Je ne sais pas vraiment pourquoi cela fonctionne, mais j'ai fait un peu d'enquête. Dans l' énumération NSLayoutPriority , il semble que le NSLayoutPriorityFittingSizeCompressionniveau de priorité soit 50. La documentation de ce niveau de priorité indique:

Lorsque vous envoyez un message fitSize à une vue, la plus petite taille suffisamment grande pour le contenu de la vue est calculée. Il s'agit du niveau de priorité avec lequel la vue souhaite être aussi petite que possible dans ce calcul. C'est assez bas. Il n'est généralement pas approprié de faire une contrainte à exactement cette priorité. Vous voulez être plus haut ou plus bas.

La documentation du fittingSizemessage référencé se lit comme suit:

La taille minimale de la vue qui satisfait aux contraintes qu'elle contient. (lecture seulement)

AppKit définit cette propriété à la meilleure taille disponible pour la vue, compte tenu de toutes les contraintes qu'elle et ses sous-vues contiennent et satisfaisant une préférence pour rendre la vue aussi petite que possible. Les valeurs de taille dans cette propriété ne sont jamais négatives.

Je n'ai pas creusé au-delà de cela, mais il semble logique que cela ait quelque chose à voir avec le problème.

Jeff Bowen
la source
Merci Jeff. Je me sens toujours comme un insecte. Apple n'a pas répondu au rdar cependant :-(
Rog
13

99,9% du temps, lorsque vous utilisez des cellules ou des en-têtes personnalisés, tous les conflits UITableViewsse produisent lorsque la table se charge pour la première fois. Une fois chargé, vous ne verrez généralement plus le conflit.

Cela se produit car la plupart des développeurs utilisent généralement une hauteur fixe ou une contrainte d'ancrage quelconque pour mettre en page un élément dans la cellule / en-tête. Le conflit se produit car lors du UITableViewpremier chargement / mise en page, il définit la hauteur de ses cellules à 0. Cela entre évidemment en conflit avec vos propres contraintes. Pour résoudre ce problème, définissez simplement les contraintes de hauteur fixes sur une priorité inférieure ( .defaultHigh). Lisez attentivement le message de la console et voyez quelle contrainte le système de disposition a décidé de briser. Habituellement, c'est celui qui a besoin de changer de priorité. Vous pouvez changer la priorité comme ceci:

let companyNameTopConstraint = companyNameLabel.topAnchor.constraint(equalTo: companyImageView.bottomAnchor, constant: 15)
    companyNameTopConstraint.priority = .defaultHigh

NSLayoutConstraint.activate([
            companyNameTopConstraint,
           the rest of your constraints here
            ])
T'ES FOU
la source
1
Belle explication.
Glenn
12

J'ai pu résoudre cette erreur en supprimant une fausse cell.layoutIfNeeded()que j'avais dans mon tableViewde cellForRowAtméthode.

Clifton Labrum
la source
1
Oui, cela a également résolu le problème pour moi. Je faisais des contraintes de disposition de code, donc j'ai d'abord pensé que je pourrais manquer quelque chose. Merci
John
1
La même chose! Merci!
Andrey Chernukha
7

Au lieu d'indiquer à la vue de table de mettre à jour ses contraintes, essayez de recharger la cellule:

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

[tableView reloadRowsAtIndexPaths:@[[tableView indexPathForCell:_briefCell]] withRowAnimation:UITableViewRowAnimationNone];

[tableView endUpdates];

UIView-Encapsulated-Layout-Height est probablement la hauteur que la vue de table a calculée pour la cellule pendant la charge initiale, en fonction des contraintes de la cellule à ce moment.

Austin
la source
J'aurais dû dire dans ma question, j'ai essayé et cela ne fonctionne pas. Je suis d'accord avec vous sur UIView-Encapsulated-Layout-Height
Rog
6
Ayez quand même la prime pour au moins soumettre une réponse. On dirait que SO le laissera simplement s'évaporer autrement.
Rog
6

Une autre possibilité:

Si vous utilisez la disposition automatique pour calculer la hauteur de la cellule (hauteur de contentView, la plupart du temps comme ci-dessous), et si vous avez un séparateur uitableview, vous devez ajouter la hauteur du séparateur, afin de revenir à la hauteur de la cellule. Une fois que vous aurez la bonne hauteur, vous n'aurez plus cet avertissement de mise en page automatique.

- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
   [sizingCell setNeedsLayout];
   [sizingCell layoutIfNeeded];
   CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
   return size.height; // should + 1 here if my uitableviewseparatorstyle is not none

}
Cullen SUN
la source
Cela m'a aidé dans un cas où j'ai eu des ambiguïtés de hauteur de disposition dans une disposition de cellules assez complexe uniquement dans certains simulateurs (iPad 6 Plus). Il me semble qu'en raison de certaines erreurs d'arrondi internes, le contenu est un peu écrasé et si les contraintes ne sont pas prêtes à être écrasées, j'ai des ambiguïtés. Ainsi , au lieu de retourner UITableViewAutomaticDimensiondans heightForRowAtIndexPathJe reviens [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] + 0.1
Leo
Je veux dire bien sûr[sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 0.1
Leo
Étonnamment; retirer le séparateur est ce qui a fait que ma table se comporte, alors merci.
royalmurder
5

Comme mentionné par Jesse dans le commentaire de la question, cela fonctionne pour moi:

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

Pour info, ce problème ne se produit pas dans iOS 10.

Phu Nguyen
la source
2
Dans Swift 4.2: self.contentView.autoresizingMask = [.flexibleHeight]
airowe
4

J'ai eu cette erreur lors de l'utilisation de UITableViewAutomaticDimension et de la modification d'une contrainte de hauteur sur une vue à l'intérieur de la cellule.

J'ai finalement compris que cela était dû au fait que la valeur constante de la contrainte n'était pas arrondie à l'entier le plus proche.

let neededHeight = width / ratio // This is a CGFloat like 133.2353
constraintPictureHeight.constant = neededHeight // Causes constraint error
constraintPictureHeight.constant = ceil(neededHeight) // All good!
Che
la source
2
C'était le commentaire qui m'a éclairé sur mon problème. J'ai une cellule d'image qui se charge dynamiquement (grandit et rétrécit) et une vue de table avec estimeRowHeight = 50 et rowHeight = UITableViewAutomaticDimension. Je brisais toujours des contraintes même si la table était à la bonne hauteur. Il s'avère que les séparateurs avaient une hauteur de 0,3333 et c'est ce qui provoquait la rupture de ma contrainte de taille d'image dans la cellule. Après avoir éteint les séparateurs, tout allait bien. Merci Che de m'avoir donné quoi chercher.
migs647
1
Dans ce cas, créez une contrainte supplémentaire, par exemple bottomMargin> = view.bottomMargin+1@900. AutoLayout essaie de prendre en compte le point supplémentaire 1, redimensionne la cellule, est confus à cause de la hauteur du séparateur, essaie de briser / relâcher certaines contraintes, trouve celui @ 900 et le supprime. Vous obtenez la mise en page que vous souhaitez sans avertissements.
Anton
sauve ma journée. quelques heures quand même
Anton Tropashko
1

Le dimensionnement de la vue texte pour l'adapter à son contenu et la mise à jour de la constante de contrainte de hauteur à la hauteur résultante ont corrigé le UIView-Encapsulated-Layout-Heightconflit de contraintes pour moi, par exemple:

[self.textView sizeToFit];
self.textViewHeightConstraint.constant = self.textView.frame.size.height;
Kim André Sand
la source
Où as-tu fait ça? Dans la mise en page
stuckj
1

Après avoir passé quelques heures à me gratter la tête avec ce bug, j'ai finalement trouvé une solution qui fonctionnait pour moi. mon principal problème était que j'avais plusieurs plumes enregistrées pour différents types de cellules, mais qu'un type de cellule en particulier était autorisé à avoir des tailles différentes (toutes les instances de cette cellule ne seront pas de la même taille). le problème est donc survenu lorsque la vue de table a tenté de retirer une cellule de ce type de la file d'attente et que sa hauteur était différente. Je l'ai résolu en mettant

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; self.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});

chaque fois que la cellule avait ses données pour calculer sa taille. Je pense que ça peut être

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

quelque chose comme

cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; cell.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});

J'espère que cela t'aides!

Alcides Eduardo Zelaya
la source
0

TableView obtient la hauteur de la cellule à indexPath du délégué. puis récupérez la cellule de cellForRowAtIndexPath:

top (10@1000)
    cell
bottom (0@1000)

si cell.contentView.height: 0 // <-> (UIView-Encapsulated-Layout-Height: 0 @ 1000) top (10 @ 1000) en conflit avec (UIView-Encapsulated-Layout-Height: 0 @ 1000),

en raison de leurs priorités sont égales à 1000. Nous devons définir la priorité supérieure sous UIView-Encapsulated-Layout-Heightla priorité de.

user2962814
la source
0

Je recevais un message comme celui-ci:

Impossible de satisfaire simultanément les contraintes ...
...
...
...
NSLayoutConstraint: 0x7fe74bdf7e50 'UIView-Encapsulated-Layout-Height' V: [UITableViewCellContentView: 0x7fe75330c5c0 (21.5)]
...
...
Tentative de récupération par contrainte de rupture NSLayoutConstraint: 0x7fe0f9b200c0 UITableViewCellContentView: 0x7fe0f9b1e090.bottomMargin == UILabel: 0x7fe0f9b1e970.bottom

J'utilise une coutume UITableViewCellavec UITableViewAutomaticDimensionpour la hauteur. Et j'ai également implémenté la estimatedHeightForRowAtIndex:méthode.

La contrainte qui me posait des problèmes ressemblait à ceci

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[title]-|" options:0 metrics:nil views:views];

Changer la contrainte en ceci résoudra le problème, mais comme une autre réponse, j'ai senti que ce n'était pas correct, car cela abaisse la priorité d'une contrainte que je veux être requise:

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-6@999-[title]-6@999-|" options:0 metrics:nil views:views];

Cependant, ce que j'ai remarqué, c'est que si je supprime simplement la priorité, cela fonctionne également et je n'obtiens pas les journaux de contraintes de rupture:

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-6-[title]-6-|" options:0 metrics:nil views:views];

C'est un peu un mystère quant à la différence entre |-6-[title]-6-|et |-[title-|. Mais spécifier la taille n'est pas un problème pour moi et cela supprime les journaux, et je n'ai pas besoin de baisser la priorité de mes contraintes requises.

user1687195
la source
0

Définir cela view.translatesAutoresizingMaskIntoConstraints = NO;devrait résoudre ce problème.

kathy zhou
la source
Cela a parfaitement fonctionné pour moi, même si ce n'était pas la solution parfaite.
Enkha
0

J'ai eu un problème similaire avec une cellule de vue de collection.

Je l'ai résolu en abaissant la priorité de la dernière contrainte liée au bas de la cellule (la dernière de la chaîne du haut vers le bas de la vue - c'est finalement ce qui détermine sa hauteur) à 999.

La hauteur de la cellule était correcte et les avertissements ont disparu.

Riches
la source