What is NSLayoutConstraint "UIView-Encapsulated-Layout-Height" and how should I go about forcing it to recalculate cleanly?
Solution 1
Try to lower the priority of your _collapsedtextHeightConstraint
to 999. That way the system supplied UIView-Encapsulated-Layout-Height
constraint always takes precedence.
It is based on what you return in -tableView:heightForRowAtIndexPath:
. Make sure to return the right value and your own constraint and the generated one should be the same. The lower priority for your own constraint is only needed temporarily to prevent conflicts while collapse/expand animations are in flight.
Addition: While it may be debatable if the system supplied constraint is correct or not, there's no point in fighting the framework. Simply accept that the system constraint takes precedence. If you think the system constraint is wrong, make sure to return the correct rowHeight from the delegate.
Solution 2
I have a similar scenario: a table view with one row cell, in which there are a few lines of UILabel objects. I'm using iOS 8 and autolayout.
When I rotated I got the wrong system calculated row height (43.5 is much less than the actual height). It looks like:
"<NSLayoutConstraint:0x7bc2b2c0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7bc37f30(43.5)]>"
It's not just a warning. The layout of my table view cell is terrible - all text overlapped on one text line.
It surprises me that the following line "fixes" my problem magically(autolayout complains nothing and I get what I expect on screen):
myTableView.estimatedRowHeight = 2.0; // any number but 2.0 is the smallest one that works
with or without this line:
myTableView.rowHeight = UITableViewAutomaticDimension; // by itself this line only doesn't help fix my specific problem
Solution 3
99.9% of the time, while using custom cells or headers, all conflicts of UITableViews
occur when the table loads of the first time. Once loaded you will usually not see the conflict again.
This happens because most developers typically use a fixed height or anchor constraint of some sort to layout an element in the cell/header. The conflict occurs because when the UITableView
first loads/being laid out, it sets the height of its cells to 0. This obviously conflicts with your own constraints. To solve this, simply set any fixed height constraints to a lower priority (.defaultHigh
). Read carefully the console message and see which constraint the layout system decided to break. Usually this is the one that needs its priority changed. You can change the priority like this:
let companyNameTopConstraint = companyNameLabel.topAnchor.constraint(equalTo: companyImageView.bottomAnchor, constant: 15)
companyNameTopConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([
companyNameTopConstraint,
the rest of your constraints here
])
Solution 4
I was able to get the warning to go away by specifying a priority on one of the values in the constraint the warning messages says it had to break (below "Will attempt to recover by breaking constraint"
). It appears that as long as I set the priority to something greater than 49
, the warning goes away.
For me this meant changing my constraint the warning said it attempted to break:
@"V:|[contentLabel]-[quoteeLabel]|"
to:
@"V:|-0@500-[contentLabel]-[quoteeLabel]|"
In fact, I can add a priority to any of the elements of that constraint and it will work. It doesn't seem to matter which one. My cells end up the proper height and the warning is not displayed. Roger, for your example, try adding @500
right after the 388
height value constraint (e.g. 388@500
).
I'm not entirely sure why this works but I've done a little investigating. In the NSLayoutPriority enum, it appears that the NSLayoutPriorityFittingSizeCompression
priority level is 50
. The documentation for that priority level says:
When you send a fittingSize message to a view, the smallest size that is large enough for the view's contents is computed. This is the priority level with which the view wants to be as small as possible in that computation. It's quite low. It is generally not appropriate to make a constraint at exactly this priority. You want to be higher or lower.
The documentation for the referenced fittingSize
message reads:
The minimum size of the view that satisfies the constraints it holds. (read-only)
AppKit sets this property to the best size available for the view, considering all of the constraints it and its subviews hold and satisfying a preference to make the view as small as possible. The size values in this property are never negative.
I haven't dug beyond that but it does seem to make sense that this has something to do with where the problem lies.
Solution 5
I was able to resolve this error by removing a spurious cell.layoutIfNeeded()
that I had in my tableView
's cellForRowAt
method.
Related videos on Youtube
Rog
iOS guy valiantly trying to wrestle JavaScript. Also available in short format: sockettrousers but that is really boring.
Updated on July 16, 2021Comments
-
Rog almost 3 years
I have a
UITableView
running under iOS 8 and I'm using automatic cell heights from constraints in a storyboard.One of my cells contains a single
UITextView
and I need it to contract and expand based on user input - tap to shrink/expand the text.I'm doing this by adding a runtime constraint to the text view and changing the constant on the constraint in response to user events:
-(void)collapse:(BOOL)collapse; { _collapsed = collapse; if(collapse) [_collapsedtextHeightConstraint setConstant: kCollapsedHeight]; // 70.0 else [_collapsedtextHeightConstraint setConstant: [self idealCellHeightToShowFullText]]; [self setNeedsUpdateConstraints]; }
Whenver I do this, I wrap it in
tableView
updates and call[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];
When I do this, my cell does expand (and animates whilst doing it) but I get a constraints warning:
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 is my calculated height, the other constraints on the
UITextView
are mine from Xcode/IB.The final one is bothering me - I'm guessing that
UIView-Encapsulated-Layout-Height
is the calculated height of the cell when it is first rendered - (I set myUITextView
height to be >= 70.0) however it doesn't seem right that this derived constraint then overrules an updated user cnstraint.Worse, although the layout code says it's trying to break my height constraint, it doesn't - it goes on to recalculate the cell height and everything draws as I would like.
So, what is
NSLayoutConstraint
UIView-Encapsulated-Layout-Height
(I'm guessing it is the calculated height for automatic cell sizing) and how should I go about forcing it to recalculate cleanly?-
Rog over 9 yearscross posted to Apple dev forums: devforums.apple.com/thread/238803
-
Rog over 9 yearsI heard a rumour that auto layout is still changing. Maybe this is a bug.
-
Rog over 9 yearsFor all those people uprooting, thanks. I would also suggest going to the Apple bug and "me too"ing it to try and bring it to the attention of an Apple dev.
-
Rog over 9 yearsMy defect report to Apple has been marked as a duplicate which I think means it is a defect.
-
Rog over 9 yearsOrtwin's suggestion of lowering the priority works in that it stops the warning but it doesn't have the correct effect on the cell. I think this is an Apple bug and encourage others to create a rdar for it.
-
Jesse over 9 yearsI resolved a similar issue in the following way and it works on iOS 7/8. 1) Lower one of the constraint priorities to 750. I would try the 1st or the 2nd 2) In the cell subclass in awakeFromNib set
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
. I think that initially setting the autoresize mask stops the last constraint from being added. I found this solution here: github.com/wordpress-mobile/WordPress-iOS/commit/… -
Rog over 9 yearsLowering the priority of one of my constraints simply lets the Apple constraint win which is not what I want - see @Ortwin's answer below. When you set the autoresizingMask set, do you also have translatesAutoresizingMaskIntoConstraints set? Either way, this sounds like mixing two systems - or an Apple bug in Autolayout.
-
orkenstein over 9 years@RogerNolan, any news? I found the same issue while playing with Auto-layout in Interface Builder. Some cells causes this issue, some not.
-
Zhang about 9 yearsCame across this problem myself today. I found that in addition to setting:
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
I also need to addself.frame = CGRectMake(0, 0, self.frame.size.width, 50);
after it and then the problem went away. I posted an answer to a similar question here: stackoverflow.com/questions/25087969/… -
Rog about 9 yearsI don't think that adding an autoresizing mark is a good solution.
-
Ell Neal about 9 years@RogerNolan are you generating this cell in IB prior to performing these layout changes? I've been debugging the same problem but I wasn't adding any extra constraints. I managed to suppress the warning by rebuilding my view from scratch, and when I diff'ed the 2 storyboard files the only difference was that the version with the warnings was missing the line
<rect key="frame" x="0.0" y="0.0" width="600" height="110"/>
in its definition, leading me to believe this is an IB bug. At least mine was anyway. -
Rog about 9 yearsI am. I will investigate this thanks.
-
QuangDT almost 4 yearsI only. get this warning when I set my tableview to editing, so it seems this warning is wrong, like so many many other auto layout warnings.
-
-
Rog over 9 yearsI should have stated in my question, I've tried this and it doesn't work. I agree on your guess about UIView-Encapsulated-Layout-Height
-
Rog over 9 yearsHave the bounty anyway for at least submitting an answer. Seems like SO will just let it evaporate otherwise.
-
Rog over 9 yearsThat would achieve exactly the opposite of what I want. The UIView-Encapsulated-Layout-Height is wrong - it belongs to the previous layout.
-
Ortwin Gentz over 9 yearsThe
UIView-Encapsulated-Layout-Height
constraint is added by UITableView once the heights are determined. I calculate the height based on thesystemLayoutSizeFittingSize
of the contentView. Here, theUIView-Encapsulated-Layout-Height
doesn't matter. Then, the tableView sets the contentSize explicitly to the value returned byheightForRowAtIndexPath:
. In this case it's correct to lower our custom constraints' priority because the tableView constraint must take precedence after the rowHeights are calculated. -
Rog over 9 years@ortwin heightForRowAtIndexPath is olde worlde isn't it? You should not implement it when you have autolayout and self sizing cells.
-
Ortwin Gentz over 9 years@RogerNolan we had to support iOS 7 as well.
-
testing over 9 years@OrtwinGentz: Still don't get the point it's correct to lower our custom constraints' priority because the tableView constraint must take precedence after the rowHeights are calculated. The thing is that
UIView-Encapsulated-Layout-Height
is wrong if I don't lower the priority... -
Rog over 9 yearsThatks Jeff. Still feels like a bug to me. Apple have not responsed to the rdar though :-(
-
Abdalrahman Shatou over 9 yearsFinally! This is a correct answer. In WWDC session, it was mentioned, if you gonna use the automatic row height sizing, then you shall set an estimatedRowHeight or bad things happens. (Yes, Apple really nasty things happened)
-
ndbroadbent about 9 yearsThis doesn't work for me... it just lets iOS break the constraint, so that my cells are shorter than I need them to be. How can I get iOS to recalculate the cell height instead?
-
Ortwin Gentz about 9 years@nathan.f77 When you change the cell size you have to tell the tableView:
[tableView beginUpdates]; [tableView endUpdates];
Then the tableView will ask the delegate via-heightForRowAtIndexPath:
for the new heights. -
ndbroadbent about 9 yearsI'm not really changing the cell heights, I'm trying to reuse an existing cell from
dequeueReusableCellWithIdentifier
. So the cell height is changing inside my tableView'scellForRowAtIndexPath
function. I would just like to get rid of thisUIView-Encapsulated-Layout-Height
constraint because it doesn't seem to be needed, since the rest of my contraints will lead to the same height. It's just preventing me from updating a reuseable cell's height. -
Ortwin Gentz about 9 yearsDon't fight the framework.
UIView-Encapsulated-Layout-Height
is automatically added byUITableView
. Since this constraint always has a priority of 1000 your vertical constraints should have a lower priority to avoid a conflict. -
Rog about 9 yearsI maintain that's avoiding conflict not fixing the actual problem - although as this still feels like an Apple bug, I think it's probably the right thing to do. Especially in light of the fact that Apple recalculate this constraint and everything does lay out correctly after the error prints.
-
Rachid Finge Jr about 9 yearsIndeed, simply setting the estimatedRowHeight property solved all my problems and thus seems mandatory. It's worth noting that rather than just setting the property to 2.0, it's better to actually estimate the row height for scrolling performance reasons.
-
JRafaelM about 9 yearswith Obj-C the only way that fixed the problem was overriding the method from the tableview delegate, thank you a lot! -(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{ return 2.0; }
-
Benjohn almost 9 yearsFWIW, adding the estimate has made no difference at all for me.
-
andrew k almost 9 years-1 for this answer since it assumes the constraint being added is correct. The added
UIView-Encapsulated-Layout-Width
in my case is just wrong, but it seems to be preferred over my explicit constraints at runtime. -
Ortwin Gentz almost 9 years@ray See my updated answer. Don't fight the framework and simply accept that the system constraint takes precedence. If you think the system constraint is wrong, make sure to return the correct rowHeight from the delegate.
-
Benjohn almost 9 yearsHeh. I'm back at this same answer again, and went to implement it with joy and hope. Once again, it's made no difference for me :-)
-
TigerCoding over 8 yearsI think what is happening is the system uses autoresizing mask constraints for a cell content view. So if you specifically set self.translatesAutoresizingMaskIntoConstraints = NO, you interfere with the layout system. Then make sure any constraint changes are done within begin and end updates, and that at least one constraint is 999 as recommended for each axis.
-
Paul T. over 8 yearsyes, that's the only correct answer for UITableViewAutomaticDimension
-
Fattie over 8 yearsHi @OrtwinGentz, thanks for the AAA tip. I had a situation where, I was using native select on rows, and then in the cell using setSelected (NOT as is more usual in the view controller) I was expanding or contracting a UILabel by a few lines. In that case the situation is exactly as you describe, as far as I can see. Can't thank you enough. RogerNolan I suspect that the subtle difference you bring to the fore (and I want it explained, too), depends on the situation. Note all readers you must use THIS INCREDIBLE TIP to resolve these issues.
-
Matt over 8 yearsEstimates returned by tableView:estimatedHeightForRowAtIndexPath: MUST be at least as large as the cell. Otherwise, the calculated height of the table will be less than the actual height, and the table may scroll up (e.g. after unwind segue returns to the table). UITableViewAutomaticDimension should not be used.
-
frangulyan about 8 years@RogerNolan this answer is not a fix for the problem but rather a dirty workaround. You should not adjust the priorities of your constraints just because there is something else created. Apple docs say that only prio 1000 is required, when you put a lower number then you eventually ask a different layout from the system. And the advice "don't fight the framework" is at least strange. The solution is simple - you just need to provide "estimatedHeightForRowAtIndexPath". In my case making it bigger than the actual height worked. More precisely, return maximum height for your cell's current state.
-
NiñoScript about 8 yearsI can confirm that making my constraint's priority be
UILayoutPriorityRequired-1
(999) and callingtableView.beginUpdates(); tableView.endUpdates()
does the trick. -
stuckj about 8 yearsWhere did you do this? In layoutSubviews?
-
Tjalsma almost 8 yearsHere it is a year later, and Apple has not clarified or corrected this cluster-code. Every developer who tries to make a custom keyboard runs into this seemingly simple issue.
-
Stan almost 8 yearsNot sure what performance impact this has (none visually), but I found that I still got issues if the intrinsic size of the subviews + constraints was larger than the estimated height. Setting the
self.tableView.estimatedRowHeight = 1000
(some arbitrary large number) eliminated the incompatible constraint. -
Jinghan Wang over 7 yearsIf the gap between estimatedRowHeight and the real height is too large, the tableView may flicker during fast scrolling.
-
Caio over 7 yearsCan' add
estimatedRowHeight
,this make my scroll buggy and makes tableView jump and flicker after any reloadCell or reloadSections. -
Natalia over 7 yearsif estimatedRowHeight doesn't work, then try
tableView.translatesAutoresizingMaskIntoConstraints = false
. I was having this constraint issue with a tableview in a container view of a primary view controller that already had the estimatedRowHeight set, and it was not until I made sure it wasn't making constraints from the autoresizing mask that I stopped getting a bunch of constraint errors. -
Mojo66 over 7 yearsNote that the lower the
estimatedRowHeight
the more oftencellForRowAtIndexPath
will be called initially. tableview height divided by estimatedRowHeight times, to be exact. On an 12 inch iPad Pro, this can potentially be a number in the thousands and will hammer the datasource and can result in a significant delay. -
Leo almost 6 yearsThat did help me in a case where I got layout height ambiguities in a pretty complex cell layout only in some simulators (iPad 6 Plus). It appears to me that due to some internal rounding errors the content is a bit squeezed and if the constraints are not prepared to be squeezed than I got ambiguities. So instead of returning
UITableViewAutomaticDimension
inheightForRowAtIndexPath
I return[sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] + 0.1
-
Leo almost 6 yearsI mean of course
[sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 0.1
-
John over 5 yearsYes, this solved the issue for me too. I was doing code layout contraints so I initially thought that I might miss something. Thanks
-
migs647 over 5 yearsThis was the comment that clued me into my issue. I have an image cell that loads dynamically (grows and shrinks) and a tableview with estimatedRowHeight = 50 and rowHeight = UITableViewAutomaticDimension. I was still breaking on constraints even though the tableview was the correct height. Turns out the separators were 0.3333 in height and that was what was kicking my image size constraint in the cell to be broken. After turning off separators all was well. Thanks Che for giving me what to look for.
-
Anton over 5 yearsIn this case create an extra constraint, e.g., bottomMargin >= view.bottomMargin+1@900. AutoLayout tries to accommodate the extra 1 point, resizes the cell, gets confused because of the separator height, tries to break/relax some constraints, finds the one @900, and discards that. You get the layout you want without warnings.
-
airowe about 5 yearsIn Swift 4.2: self.contentView.autoresizingMask = [.flexibleHeight]
-
Andrey Chernukha about 5 yearsThe same thing! Thanks!
-
Anton Tropashko about 5 yearssave my day. few hours anyway
-
Enkha almost 5 yearsThis worked perfectly for me, even if it was not the perfect solution.
-
royalmurder over 4 yearsStaggeringly; removing the separator is what got my table behaving, so thanks.
-
FateNuller about 4 yearsSetting
estimatedRowHeight
on the table view and then returningautomaticDimension
from the row height delegate method worked for me. Previously I was not setting theestimatedRowHeight
and autolayout was spitting a conflicting constraint message. If you are attempting to setautomaticDimension
for the height, and addingestimatedRowHeight
does not work for you, make sure that the cell you're trying to render has a clear intrinsic content size or else your cell probably will not render correctly. -
Glenn Posadas about 4 yearsBeautiful explanation.
-
mschmidt about 4 yearsI had a similar problem. It worked for me after following these steps: 1) setup
tableView.rowHeight
toUITableView.automaticDimension
, 2) setuptableView.estimatedRowHeight
to a sane value, e.g. 44, and 3) update constraints only withinupdateConstraints
before the call tosuper
. I previously updated my constraints inlayoutSubviews
which seemed to be the cause for the warning in my case. -
אורי orihpt over 3 yearsWhere do you put it?
-
Sourabh Shekhar over 3 yearsAwesome explanation.
-
Lucian Boboc over 3 yearsThank you, i struggled with a similar problem today and the only solution that worked was to decrease the bottom priority to 999.
-
Van over 3 yearsThank for exact solution and self explanatory answer... saved my day...
-
gngrwzrd about 3 yearsOMG I've tried everything. This finally worked!
-
Frank almost 3 yearsIf Apple won't fix this problem, it may be called a fix. (But I prefer to call it
workaround
🤪) -
surfrider almost 3 yearsThat's right, thanks. Worked for me. But I can't undesrstand logic behind that and can't see that's the cell's height is 0 in the moment of this error. The logs in console shows something like this
'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fa661635050.height == 150
. It's meaning that the cell's content view got it's height. And for me there is no views with height of 0. -
Nirav Jain over 2 yearsThank you!...this is working pretty fine with table view cells as well.
-
Naveen Shan about 2 yearsI tried everything and this works, but I need separator :(
-
K_Mohit about 2 yearsMaybe you can add a 1px view in cell itself at the bottom.