How to setup autolayout constraints properly for self-sizing cells - ios

I've seen a lot of guides about self-sizing cells in iOS 7 and 8. Unfortunately, all of them shows very simple cases (I mean two UILabel's with same width and which placed like the first under the second and their constraints are very simple too). So I've a situation that seems to be not so much difficult but I can't resolve a problem.
I believe that it is very easy for the most of people and hope that somebody can help. The problem is that there is UITableViewCell with 3 child views inside.
Every view is UILabel. Labels placed in such order: two labels at the top of UITableViewCell with fixed size and one at the bottom with fixed width but with dynamic height. How should I setup my constraints properly to make my UITableViewCell be self-sizing?

The key to self-sizing is a vertical set of constraints that will determine the cell height. This isn't that complex, as you don't have two horizontal labels that vary in height.
Since headerLabel and dateLabel height don't vary, you only need to constrain the dynamicLabel to one of the two top labels.
In this example, we'll arbitrarily pick headerLabel to use in the vertical constraints. The spacing between the labels wasn't specified, so I'll assume it's 10. Adjust as necessary.
For your horizontal constraints, set leading and trailing space to the superview.
Your vertical constraints would simply look like "V:|-10-[headerLabel]-10-[dynamicLabel]-10-|" (and should
dynamicLabel will grow as tall as it needs, provided you set its numberOfLines to 0, and its height will end up determining the height of the cell.
Assuming the constraints are setup properly, and the storyboard cell height exactly matches the vertical height set by the constraints, you shouldn't see any storyboard warnings or errors, and should be good to go.
8.4 does address a number of issues, so you shouldn't require any specific (layoutSubViews/preferredMaxLayoutWidth/reloadData) code to work around earlier 8.x problems.
As an aside, a general tip is to pin your constraints to the superview margin, instead of the superView. This means your leading, trailing, top, and bottom constraints could be 2, instead of 10, since the margin is generally 8. This lets your white space adapt to different devices, which is a really nice touch.

Related

NSLayoutContraints: How to position a bottom view below the higher of two labels?

I am working on an iOS 11+ app and would like to create a view like in this picture:
The two labels are positioned to work as columns of different height depending on the label content. The content of both labels is variable due to custom text entered by the user. Thus I cannot be sure which of the the two labels is higher at runtime and there is also no limit to the height.
How can I position the BottomView to have a margin 20px to the higher of the two columns / labels?
Both labels should only use the min. height necessary to show all their text. Thus giving both labels an equal height is no solution.
I tried to use vertical spacing greater than 20px to both labels but this leads (of course) to an Inequality Constraint Ambiguity.
Is it possible to solve this simple task with Autolayout only or do I have to check / set the sizes and margins manually in code?
You can add labels to stackView
One way to do this is to assign equal height constraint to both label. By doing this height of label will always be equal to large label (Label with more content).
You can do this by selecting both labels and adding equal height constraint as mentioned in screen short.
Result would be some think like below
The answer given as https://stackoverflow.com/a/57571805/341994 does work, but it is not very educational. "Use a stack view." Yes, but what is a stack view? It is a view that makes constraints for you. It is not magic. What the stack view does, you can do. An ordinary view surrounding the two labels and sizing itself to the longer of them would do fine. The stack view, as a stack view, is not doing anything special here. You can build the same thing just using constraints yourself.
(Actually, the surrounding view is not really needed either, but it probably makes things a bit more encapsulated, so let's go with that.)
Here's a version of your project running on my machine:
And with the other label made longer:
So how is that done? It's just ordinary constraints. Here's the storyboard:
Both labels have a greater-than-or-equal constraint (which happens to have a constant of 20) from their bottom to the bottom of the superview. All their other constraints are obvious and I won't describe them here.
Okay, but that is not quite enough. There remains an ambiguity, and Xcode will tell you so. Inequalities need to be resolved somehow. We need something on the far side of the inequality to aim at. The solution is one more constraint: the superview itself has a height constraint of 1 with a priority of 749. That is a low enough priority to permit the compression resistance of the labels to operate.
So the labels get their full height, and the superview tries to collapse as short as possible to 1, but it is prevented by the two inequalities: its bottom must be more than 20 points below the bottom of both labels. And so we get the desired result: the bottom of the superview ends up exactly 20 points below the bottom of the longest label.

In a UITableViewCell used with autosizing, what is missing from the vertical constraints to make the height as small as possible?

I'm trying to use dynamic heights in a UITableView with a specific cell layout. Consider the following illustrative representation of that layout:
I have the horizontal constraints working properly (15px from both edges, 15px between them, equal widths) but I'm struggling with the vertical constraints. Here are the vertical requirements for this layout:
The vertical intrinsic content size of both the green and blue rectangles are based on external data which is passed to the cell at the time of creation.
Both rectangles are vertically centered within their superview
There will always be a minimum space of 15px between the top/bottom edges of the rectangles and the respective edges on the superview. In other words, whichever one is taller dictates the height of the superview (i.e. the cell)
To that end, here's what I have constraint-wise so far:
Vertical center constraints for both rectangles
Height constraints of the rectangles equal to or less than the height of the superview minus 30 (i.e. if the rectangle's height is 40, the superview must be a minimum of 70. This theoretically achieves the same effect as setting separate top and bottom '>= 15' constraints while using two less.)
Vertical content Hugging on the superview set to 'required' (i.e. 1000)
The third point is because the second points together only define the minimum height for the superview (yellow), but not a maximum. In theory, if it had a height of 10,000 it would still satisfy those constraints.
My thought is setting its content hugging to 'required' would make the superview as short as possible without violating the other constraints, thus at all times, either the green rectangle or the blue rectangle would be 15 px from the edge depending on whichever was taller. However, the height still seems to be 'stretched out' as seen here...
Note: The views on the inside are properly vertically centered and correctly maintain a minimum distance from the top/bottom edges. The problem I'm trying to solve is restricting the height of the superview to be as small as possible.
It doesn't appear that I'm getting any ambiguous constraint messages (I don't see anything in the logs, but I believe I should be because again <= constraints aren't enough on their own, so I'm not sure exactly how to use the tools to debug this, or to find out which constraint is driving the height.
So, can anyone help?
P.S. To ensure it wasn't something external to the cell, like forgetting to configure auto-heights for the UITableView, I removed the two rectangles and replaced them with a simple multi-line label pinned to all four edges. When I ran it with that, the cell properly shrank in size as expected. I bring that up to hopefully stave off answers suggesting that's potentially the problem.
Reading the requirements you provided I have added constraints shown below:
For demonstration purpose I have used a simple view container instead of a cell. Since inner boxes will have intrinsic content size derived externally, I have taken labels to mimic that.
To reiterate, constraints added are:
Horizontal
Container view(orange) has leading and trailing constraints with the super view.
Inner views has leading, trailing constraints with 15points of space.
Both labels have leading and trailing constraints with 9 points.
Both inner views have equal width constriant.
Vertical
Container view is vertically in center.
Both inner views have vertically center constraint applied.
Both inner views have top and bottom constraints with >= 15 condition.
Both inner labels have top and bottom constraints with their super views.
I set the no. of lines property of both labels to zero so that they can grow at run time.
At runtime I changed the text of one of the label and the box and container grew accordingly.
To refresh your cell height implement heightForRow method and provide the latest height. A typical snippet will look something like this (assuming you have already initialized the cell):
cell.needsUpdateConstraints()
cell.updateConstraintsIfNeeded()
cell.contentView.setNeedsLayout()
cell.contentView.layoutIfNeeded()
let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
return height
Hope this will help.
Ok, so I was going in the right direction with the content hugging, but the correct way to handle this was to specify a height constraint on the yellow view of 0 and with a low priority. I used 100 to be even lower than the default 250. When I did that, the solver tries to get as close to zero as it can while still respecting the other constraints, therefore 'hugging' the content. Still don't know why content hugging on its own didn't work, but that addressed the issue.

Why does Autolayout not produce consistent results?

Either I'm out of my mind, or AutoLayout is straight broken. Can someone please explain this to me. I have a TableViewCell in a TableView that spans the width of the ViewController. I put 4 Labels inside my TableViewCell. I created constraints using AutoLayout such that each label is 25% the width of the TableViewCell. And yet, the 4 labels are CLEARLY different widths and they don't even add up to 100% of the width of the entire cell. Here's the screenshot. (Horizontal position of each of the labels is ambiguous, yes, but that shouldn't make a difference). Why are they not the same width? And why does 25% + 25% + 25% + 25% not add up to 100%? Running XCode 7.2 and targeting iOS 9.
This red error symbol is Interface Builder telling you that it cannot solve your constraints. In this case, as you have said, it's likely because you have not provided x position constraints for the labels.
Auto layout can either solve all constraints and get a right layout, or it can't and the result will be undefined. Remember that auto layout is an algebra-based process that solves for unknown values by using known values that you provide in constraints. If you don't provide sufficient and unambiguous known values, the equations for the remaining values simply cannot be solved and there can be no expectation of a correct result. The solution is to create enough constraints to make the layout solvable.
As a note, as of iOS 9 I would suggest using a UIStackView to hold those labels. UIStackView exists precisely to take the pain out of setting up manual constraints for these types of scenarios. If you used a horizontal stack view in the cell, you would constrain its edges to the cell's edges, drag the 4 labels into it and set it to "Fill Equally". And that's all you would need!
Daniel Hall's answer has useful information, but doesn't tell you specifically why you're seeing what you're seeing, so I will.
Xcode doesn't always enforce your constraints in the storyboard editor until you ask it to. In this case, you can select the table view cell's content view and from the menu bar choose Editor > Resolve Auto Layout Issues > All Views in BBRowTableViewCell > Update Frames. (Sometimes it takes two or three tries for Xcode to get everything right.)
However, you probably won't like the result. Because you haven't constrained the horizontal positions of the labels, Xcode will probably pile them all up at the left edge of the cell, or maybe somewhere outside the bounds of the cell where you can't even see them.
If your deployment target is iOS 9 or later, the easiest solution (as Daniel Hall said) is to put the labels in a UIStackView set to “Fill Equally”, and constrain the stack view's edges to the cell content view's edges.
If your deployment target is earlier than iOS 9, then you should create the constraints described by user3802077.
This is not the only way, but here is how I usually do it.
As you did for the top and bottom for each labels, then:
Leading of label1 to leading of superview
Trailing of label4 to trailing of superview
Then a constraint for each neighbouring label:
label1.trailing to label2.leading,
...
Then put an equal width constraint from each labels to label1:
label2 to label1
label3 to label1
label4 to label1
This should be it. No need of specifying 25%.
For autolayout constraints it requires to give for constraint to particular object
x,y position and hight , width
If you are not giving any one of this it shows error to you.
So make sure to give all the require constraints to your object.
Other option is uistackview for the ios 9 and later.
Here is a link for you to learn about stackview
https://m.youtube.com/watch?v=XqVWyA5PLwk

UIScrollView with multiple multi-line labels and AutoLayout?

Is there a way to achieve this? I have tried literally everything and nothing has worked for me yet.
So basically what i want to do is the following: I have a scroll view with some labels in it. All the labels get their text from a server and I have set their number of lines to 0 so that they change their height according to the amount of text. However, this does not affect the scrollview content size(even though my labels have constraints set up to the bottom,top,leading and trailing of the scrollview) and the labels go off screen and I am unable to scroll down. Can someone point me in the right direction to how I would set up my constraints, my view hierarchy and etc?
Any help is much appreciated! :)
Late, but this solved it for me:
Set leading (I have a 32pt inset), trailing and top constraints. The trailing will not actually seemingly do anything..
Make the trailing Greater Than or Equal to avoid localization alert.
Finally, add a new Equal Width constraint to the label matching the scrollview. Use the constant to subtract the required padding (I used 64 due to mirror my leading inset).
And voilà! The Label will align correctly both in IB and in-app.
In Scrollview the last view's bottom constraint is so important. You should set its priority to 250 and put it to Greater than or equal.
Remember you should only change the bottom constraint of the last view, which in my case it's the continue button.
I would consider using UITableView instead, it has several benefits:
It allows for reuse of cells, if all the cells look the same
It manages recycling of cells when the number of values you're getting from the server increases (decreases memory pressure when number of cells becomes substantial)
It allows for more flexibility with the content (it's quite often for design to change last second or to evolve over the course of the project)
Most importantly, UITableView support auto sizing cells (as of iOS8), you need to specify the constraints between the label and the borders of the cell
There are several resources to start with:
http://www.raywenderlich.com/73602/dynamic-table-view-cell-height-auto-layout
https://www.captechconsulting.com/blogs/ios-8-tutorial-series-auto-sizing-table-cells
http://www.appcoda.com/self-sizing-cells/
Use a container view in a scrollView
Add constraints to superview (leading, trailing, top, bottom, height,width)
Make IBOutlet of constraints that you are going to update.
Add you all labels inside that view.
Update constrains/frame of your label so that it fits the text.
How much you increase the label height you should increase the container height too.
If the label count is not fixed use custom label class to add subview.
Perhaps you should need to understand how ScrollView works in Storyboard with autolayout.

Why when using self-sizing cells in iOS 8 does the constraint to the bottom of the cell have to be "greater than/equal" or use vertical hugging?

I'm trying to use Auto Layout to take advantage of the self-sizing UITableViewCells.
I have one UILabel at the top of the cell and another beneath it. I set the top constraints to be pinned to the top, left and right, and the bottom to be pinned to the top left and right of the upper label.
Now as I go to set the bottom label's final constraint (its distance from the bottom) I set it to 10pt from the bottom of the cell. However this sparks a bunch of Auto Layout complaints. It says I have to make one of the labels have a higher vertical hugging priority. Why is this?
And in the WWDC video, the engineer sets the bottom one to be greater than or equal, instead of just equal. This seems like a poor solution at least in my case, because I never want it to be greater than what I set.
The IB issue you are seeing is due to the fact that the content that you currently have in the labels and all your constraints doesn't fit the UITableViewCell height (it doesn't autosize in IB yet). The autosizing happens at runtime, and setting it to greater than or equal is fine as the code autosizing will find the smallest size that acceptably fits all your content.

Resources