Mise en page automatique: Qu'est-ce qui crée des contraintes nommées UIView-Encapsulated-Layout-Width & Height?
Mes contraintes de présentation conviennent parfaitement dans Interface Builder, mais une exception se produit lors de l'exécution, grâce à une partie du cadre qui applique des contraintes de hauteur et de largeur fixes que je ne souhaite vraiment pas. Pourquoi sont-ils là et comment les désactiver?
Ce sont les deux dernières contraintes figurant dans la liste consignée:
2014-04-26 09:02:58.687 BBCNews[32058:60b] 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:0xbf478a0 UIView:0xbf4a3c0.height == 0.28125*UIView:0xbf4a3c0.width>",
"<NSLayoutConstraint:0xbf47190 UIView:0xbf4a3c0.leading == BNMyNewsCell_landscape:0xbf48b10.leading>",
"<NSLayoutConstraint:0xbf47160 UIView:0xbf4a3c0.trailing == BNMyNewsCell_landscape:0xbf48b10.trailing>",
"<NSLayoutConstraint:0xbf47130 BNMyNewsCell_landscape:0xbf48b10.bottom == UIView:0xbf4a3c0.bottom>",
"<NSLayoutConstraint:0xbf47100 UIView:0xbf4a3c0.top == BNMyNewsCell_landscape:0xbf48b10.top>",
"<NSLayoutConstraint:0xd4c3c40 'UIView-Encapsulated-Layout-Width' H:[BNMyNewsCell_landscape:0xbf48b10(304)]>",
"<NSLayoutConstraint:0xd4c38a0 'UIView-Encapsulated-Layout-Height' V:[BNMyNewsCell_landscape:0xbf48b10(290)]>"
}
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0xbf478a0 UIView:0xbf4a3c0.height == 0.28125*UIView:0xbf4a3c0.width>
Sur la base d’une tonne d’observation, je crois (mais je ne peux pas savoir avec certitude) que les contraintes nommées UIView-Encapsulated-Layout-Width
et UIView-Encapsulated-Layout-Height
sont créées par UICollectionView
et friends et existent pour appliquer la taille renvoyée par la méthode déléguée sizeForItemAtIndexPath
. Je suppose que c'est là pour veiller à ce que la UICollectionViewCell
configurée par cellForItemAtIndexPath
finisse à la taille qui lui a été annoncée.
Ce qui répond à ma question initiale ici. Une deuxième question est pourquoi les contraintes étaient insatisfiables? La hauteur intrinsèque de la cellule doit être identique à UIView-Encapsulated-Layout-Height
. Encore une fois, je ne suis pas certain, mais je soupçonne qu’il s’agissait d’une erreur d’arrondi (c’est-à-dire que la hauteur intrinsèque atteignait 200,1 pixels, le UIView-Encapsulated-Layout-Height
pouvait être arrondi à 200. Le correctif que j’ai proposé était simplement de réduire la priorité de la cellule concernée contrainte pour autoriser UIView-Encapsulated-Layout-Height
à avoir le dernier mot.
Cela ne répond peut-être pas à votre question, mais pourrait aider d'autres personnes comme moi qui sont arrivées ici après une recherche.
J'obtenais une erreur de contrainte cassée AutoLayout étrange accompagnée d'une contrainte UIView-Encapsulated-Layout-Width
parce que j'ajoutais une tableHeaderView
à une vue de table qui n'avait pas encore été redimensionnée avec AutoLayout. Le système essayait donc d'appliquer les contraintes de mes sous-vues d'en-tête à l'intérieur d'une table avec une trame de {0,0,0,0}
. Puisque UITableView aime contrôler la largeur de ses éléments, sa contrainte de largeur générée, UIView-Encapsulated-Layout-Width
, a été définie sur zéro, ce qui a créé toute sorte de confusion avec les éléments de mon en-tête qui attendaient une largeur de 320 + pt.
La livraison: assurez-vous que vous ajoutez/manipulez vos vues supplémentaires/en-tête/pied de page après que la vue de table a été redimensionnée par AutoLayout.
J'étais confronté à la même contrainte étrange et je ne savais pas pourquoi, jusqu'à ce que je me souvienne de la foutue propriété translatesAutoresizingMaskIntoConstraints
. Définir ce paramètre sur false
a résolu le problème . En arrière-plan, les masques de redimensionnement automatique (l'ancien moteur de présentation pour iOS) sont convertis en contraintes. Très souvent, vous ne voulez pas ces contraintes et vous voulez les vôtres. Dans de tels cas, vous devez définir cette propriété sur false et tout ira bien:
view.translatesAutoresizingMaskIntoConstraints = false
Nous avons commencé à voir des tonnes de conflits de disposition dans iOS 11 qui incluent des références à ces contraintes. Elles sont en fait ajoutées via l'indicateur translatesAutoresizingMaskIntoConstraints
. Il semble que dans iOS 11, il y ait beaucoup plus de magie de mise en forme automatique lorsqu'une vue est ajoutée à la hiérarchie plutôt que juste lorsque la vue est présentée (comme cela semblait fonctionner dans les versions précédentes d'iOS).
C'est le cas que nous avons rencontré:
- Créez une vue dont la disposition interne aide à définir la taille des vues (par exemple, la vue a des contraintes internes qui incluent un remplissage explicite, etc.)
- *** Ajoutez cette vue à la hiérarchie.
- Définissez translatesAutoresizingMaskIntoConstraints sur false quelque temps plus tard, avant que la présentation ne passe.
La deuxième étape (***) entraînera un conflit car le système n’ajoutera aucune contrainte de taille à la vue au moment de son ajout à la hiérarchie. Nous avons défini translatesAutoresizingMaskIntoConstraints
ultérieurement à la suite de l'utilisation du framework PureLayout qui définit automatiquement cet indicateur correctement lorsque vous contraignez la vue ... Cela dit, dans iOS 11, vous devez vous rappeler de désactiver translatesAutoresizingMaskIntoConstraints
au moment de la construction, avant d'ajouter la vue à la hiérarchie.
Je suppose qu'Apple pensait que le fait de remettre YES à cet indicateur serait beaucoup plus utile que douloureux. Malheureusement, cela n'a pas été le cas.
J'ai eu cette erreur dans toutes sortes de circonstances (pas nécessairement lié à UICollectionView et à ses amis comme suggéré par le correct répondre ici) ..
Donc, ma façon de faire était tout simplement de supprimer toutes les contraintes, puis de les reconstruire (sauf que cette fois, je ne crains pas que mes contraintes se heurtent à celles déjà créées):
donc dans le code:
UIView *parentView = [viewInQuestion superview];
[parentView clearConstraintsOfSubview:viewInQuestion];
où clearConstraintsOfSubview
est une méthode de catégorie sur UIView:
- (void)clearConstraintsOfSubview:(UIView *)subview
{
for (NSLayoutConstraint *constraint in [self constraints]) {
if ([[constraint firstItem] isEqual:subview] || [[constraint secondItem] isEqual:subview]) {
[self removeConstraint:constraint];
}
}
}
Voir certainement ceci sur une UITableView
's tableHeaderView
. J'ai réussi à faire en sorte que cela fonctionne avec une vue d'en-tête personnalisée en définissant explicitement la largeur sur celle de la variable tableView
après la définition de la variable tableHeaderView
, THEN après la fin de la passe de présentation.
Exemple de code pour iOS 9, qui suppose que vous avez passé une UITableView
dans votre méthode en tant que tableView
et un élément pour le configurer en tant que item
:
//Create the header view
self.contentDetailHeaderView = MyCustomHeaderView()
//Turn on autolayout
self.contentDetailHeaderView.translatesAutoresizingMaskIntoConstraints = false
//Add the header to the table view
tableView.tableHeaderView = self.contentDetailHeaderView
//Pin the width
let widthConstraint = NSLayoutConstraint(item: self.contentDetailHeaderView,
attribute: .Width,
relatedBy: .Equal,
toItem: tableView,
attribute: .Width,
multiplier: 1,
constant: 0)
tableView.addConstraint(widthConstraint)
//Do whatever configuration you need to - this is just a convenience method I wrote on my header view.
self.contentDetailHeaderView.setupForItem(item)
//Lay out the configured view
self.contentDetailHeaderView.layoutIfNeeded()
//Reset the table header view, because ¯\_(ツ)_/¯
tableView.tableHeaderView = self.contentDetailHeaderView
Quelques notes, surtout pour quand je regarde ça à nouveau parce que j'ai la mémoire d'un poisson rouge:
- Not devez appeler ceci depuis
viewDidLayoutSubviews
- J'ai pu utiliser cette technique tant quetableView
a la largeur appropriée lors de l'installation. - Vous devez vous assurer que votre vue en-tête est configurée pour se redimensionner automatiquement. Je l'ai fait en créant un
.xib
, puis en m'assurant que tous les éléments étaient bien épinglés de sorte que la hauteur changée par la vue, la hauteur se modifie ensuite. - Si vous essayez de faire ceci pour
viewForHeaderInSection
, vous feriez probablement mieux d’obtenir quelque chose hors écran que vous pouvez disposer à la cette technique . Je n'ai pas eu beaucoup de chance avec les bits d'auto-dimensionnement.
Je faisais face à un problème similaire et l'ai résolu avec ce qui suit.
Environment: Swift 5.0, xcode 10.2.1, Définition de vues par programme
Message d'avertissement: Impossible de satisfaire simultanément les contraintes ... 'UIView-Encapsulated-Layout-Width' UIView: 0x0000000000.width == 0 (actif)> ")
Code avec avertissement
override func loadView() { view = UIView() /// Adds the subviews to the view and sets their properties and constraints setupViews() }
Code qui a effacé l'avertissement
override func loadView() { /// Needed to set the frame of the root view to the window frame. let window = UIWindow() view = UIView(frame: window.frame) /// Adds the subviews to the view and sets their properties and constraints setupViews() }
Remarques sur la méthode loadView (): "Si vous utilisez Interface Builder pour créer vos vues et initialiser le contrôleur de vue, vous ne devez pas remplacer cette méthode. Vous pouvez remplacer cette méthode afin de créer vos vues manuellement Si vous le souhaitez, attribuez la vue racine de votre hiérarchie de vues à la propriété view. Les vues que vous créez doivent être des instances uniques et ne doivent pas être partagées avec un autre objet du contrôleur de vue. L'implémentation personnalisée de cette méthode ne doit pas appeler super." - Apple documentation
Notes sur la vue racine:
"Si vous préférez créer des vues par programme ... vous le faites en remplaçant la méthode loadView de votre contrôleur de vue. Votre implémentation de cette méthode doit effectuer les opérations suivantes:
Créez un objet de vue racine. La vue racine contient toutes les autres vues associées à votre contrôleur de vue. Vous définissez généralement le cadre de cette vue pour correspondre à la taille de la fenêtre de l'application, qui elle-même doit remplir l'écran. Cependant, le cadre est ajusté en fonction de l'affichage de votre contrôleur de vue. Voir "Redimensionnement de la vue du contrôleur de vue".
Vous pouvez utiliser un objet UIView générique, une vue personnalisée que vous définissez ou toute autre vue pouvant être redimensionnée pour remplir l'écran.
Créez des sous-vues supplémentaires et ajoutez-les à la vue racine. "- Ancienne Apple documentation?
Dans mon cas, par inadvertance, j'ai défini deux fois mes contraintes de programmation. Dès que j'ai supprimé l'appel en double, les conflits ont disparu.
J'ai eu le même problème lors de l'ajout de contraintes à un en-tête de vue Table. Cela semble se produire lors de l'ajout de contraintes avec des constantes définies lorsque les limites de l'en-tête étaient (0,0,0,0). J'ai réussi à résoudre ce problème en ajoutant uniquement les contraintes dans la méthode de présentation des sous-vues lorsque les limites de l'en-tête n'étaient pas (0,0,0,0)
if self.bounds == CGRect.zero {
return
}
J'avais un problème similaire trouvé en testant Split View sur l'iPad Pro, et la réponse de DesignatedNerd a fonctionné, mais je n'ai pas eu besoin de tant de code. Voici ce que j'ai utilisé:
[self.tableView.tableHeaderView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:self.myTableHeaderView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.tableView
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0];
NSLayoutConstraint *yConstraint = [NSLayoutConstraint constraintWithItem:self.myTableHeaderView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.tableView
attribute:NSLayoutAttributeTop
multiplier:1
constant:0];
[self.tableView addConstraints:@[widthConstraint, yConstraint]];
Notez l'ajout de la contrainte Y, qui lie le haut de la tableHeaderView au haut de la tableView.
Après avoir frappé ma tête pendant un moment, j'ai trouvé ce lien . Dans mon cas, cela se passait sur UITableViewHeaderFooterView lorsque j'utilisais insertRows ou deleteRows de mon UIVieController. 'estimationSectionHeaderHeight' et 'estimationRowHeight' où ils sont définis, mes contraintes ont été refaites 3 fois ... L'erreur affichée était:
"<NSLayoutConstraint:0x280648140 H:|-(8)-[UIImageView:0x106e94860] (active, names: '|':DAT_Air_Vinyl.ExportsAlbumTableViewHeader:0x106e8fb50 )>",
"<NSLayoutConstraint:0x280648230 H:[UIImageView:0x106e94860]-(8)-[DAT_Air_Vinyl.MainLabel:0x106ea5750'The Coral - The Invisible...'] (active)>",
"<NSLayoutConstraint:0x280648410 H:[UIButton:0x106ea5a40]-(8)-| (active, names: '|':DAT_Air_Vinyl.ExportsAlbumTableViewHeader:0x106e8fb50 )>",
"<NSLayoutConstraint:0x280648460 H:[DAT_Air_Vinyl.MainLabel:0x106ea5750'The Coral - The Invisible...']-(8)-[UIButton:0x106ea5a40] (active)>",
"<NSLayoutConstraint:0x2806493b0 'UIView-Encapsulated-Layout-Width' DAT_Air_Vinyl.ExportsAlbumTableViewHeader:0x106e8fb50.width == 0 (active)>"
Comme indiqué sur le lien:
" Lorsque vous effectuez insertRows ou deleteRows avec certains types d'animation, UIKit animera la hauteur de la ligne de 0 à la hauteur maximale ou arrière. À la fin 0 de cette animation, il est impossible de résolvez si tout l’axe vertical est défini sur priorité = 1000. Mais abaissez une seule contrainte à 999 - dites l’espace en bas à la marge de super vue - et tout va bien, le contenu sera simplement déroulant, en dehors des limites de la cellule. ".
La solution consistait à définir sur 999 (ou inférieur à 1000) la priorité principale de UIImageView.
la contrainte UIView-Encapsulated-Layout-Height
est créée avec la valeur que vous avez définie dans tableView.estimatedSectionHeaderHeight