Say I have a view called container. container contains 5 UIButtons. I want to add a height NSLayoutConstraint on container, and this height should be equal to the NSLayoutHeightAttribute of the tallest button in its subviews.
I don't see a straightforward way to do this. Anyone have any ideas?
You need one constraint for each subview (button), specifying that the container's height should be greater than or equal to the subview's height. Give that constraint a high priority, like UILayoutPriorityRequired (which is the default anyway).
Then add one more constraint on the container's height, specifying that it should have a height equal to zero. Give that constraint a low priority, like UILayoutPriorityLow. Since auto layout tries to minimize the error of unsatisfied constraints, it will make the container as short as possible while still satisfying all higher-priority constraints.
I have put an example in this gist. It produces this result:
The blue views have fixed heights. The tan view is the superview of the blue views and its height is constrained as I described above. I pinned each subview's bottom to the container's bottom, but you could pin the tops or the Y centers instead.
Related
I tried trailing,leading,bottom,top as well as center/width/height constraint-sets. In both cases Xcode complains about ambiguous position unless I wrap the stackview in a UIView inside the cell.
Is this supposed to work like this or am I missing something?
You have too many constraints and they likely conflict. The system needs one position and one size for each dimension. If you give it two positions then it automatically infers the dimension -- ie if you give it top and bottom then it auto calculates the height, so you shouldn't also be specifying the height. On top of that the stack view can actually determine its own height if all of its elements have an intrinsic size. In that case you can just specify a single position constraint.
TLDR just use top, bottom, leading, trailing constraints and the system will infer the width and height.
Simple goal, but it demonstrates situations where Auto Layout gives me a headache. I want to have a stack view with a width of 256pt and a dynamic height based on layout (I shouldn't have to manually specify the height).
Inside it should be an image view sized 64pt x 64pt, which should also be centered horizontally as well as constrained 8pt from the superview's top. Note that the image view isn't the only child, hence why the stack view's height must be sized dynamically.
Auto Layout now tells me there's a conflict between the 256pt width constraint of the stack view and the 64pt width constraint of the image, as well as some mysterious "leading = Image.leading" and "trailing = Image.trailing" conflict which I can't even delete nor find.
Am I missing out something here regarding Auto Layout? I expect all logic to be contained in the interface builder, so no code should be required.
Running Xcode 9.1
Layout image
There is nothing to confuse. iOS clearly telling you the issue.
StackViews take size based on the size of child components (this is called implicit size) unless its been overriden manually as in your case which is 256pt.
Because stackView is just a container for multiple childViews stacked either horizontally or vertically, now because you have added only one imageView to it, it adds the leading and trailing constraint to it which makes absolute sense because you added a single view to the stack of view's , now what should stackView do? stretch childView (in your case imageView) to its own size.
But then you did not allow it because you added width constraint to imageView now when it tries to increase the imageView's width imageView's constraint wont allow it.
Hence it is complaining that there are too many conflicting constraints. Thats all :)
some mysterious "leading = Image.leading" and "trailing =
Image.trailing" conflict which I can't even delete nor find.
You cant delete them because, imageView is the only view inside stackView. Because there is only one child view to stack, stackView will start from left side (leading) to right side (trailing). Because now stackView has its own width it tries to change the width of imageView to reflect the same! But images width constraint prevents it from happening.
What are you trying to achieve with imageView added to stackView. If there is only one view in stackView, adding stackView does not make any sense. Reconsider what you are doing.
Finally, when you have only one childView in stack view, adding horizontal center does not make any sense (no matter vertical/horizontal stackView).
I am trying to achieve the following.
I have a
--> MainView
--> UIImageView 200x200
--> UILabel W:200(max) , H: Variable
--> UILabel W:Variable , H: 20
All the views in mainView are placed in sequence one after another.
Now i am trying to set autoLayout so that the mainView height is depended on its children,
E.g If i set ImageView hidden then it should wrap both UILabels etc.
How can I set autoLayout constraints so that the mainView have "Wrapping" effect over its children.
The easy way to achieve this is using a UIStackView (WWDC 2015 session video). Pure Autolayout is a lot more complicated in this case.
Assuming you would like to lay the children out vertically, left-aligned:
Controlling the Trailing Edge
Add a greater-than-or-equal 0 constraint between the trailing edge of each child and the parent's trailing edge. This will cause the widest child to push the parent's trailing edge to the right. These constraints should have a very high priority.
You will need another constraint to prevent the layout from being ambiguous. With the three trailing constraints the width of the parent is ensured not to be smaller than the widest subview. You also have to constrain the parent's width not to be greater than the widest subview's width. Just add a width constraint to the parent with a constant of 0 and a very low priority.
I like to think of that low-priority width constraint to work like a rubber band trying to pull the trailing edge as far to the left as it can. The greater-than-or-equal-to-zero constraint of the widest subview prevents it from pulling any further.
Hiding views does not have an effect since hidden views still take part in the layout calculation. You will need to keep a references to the greater-than-or-equal constraints and disable the corresponding constraint when hiding a child to take it's trailing edge out of the equation.
Controlling the Height
The heights of the children are likely defined by the view's intrinsic content size. Conceptually the Autolayout engine adds width and height constraints to the view according to the settings for content hugging and content compression resistance.
There will be two hidden height constraints for views that have an intrinsic height dimension: one for content hugging and one for content compression. Hugging constrains the height to be less than or equal to the intrinsic height. Compression resistance constrains the height to be greater than or equal to the intrinsic height. The height of the view is exactly equal to the intrinsic height if both can be fulfilled. The priorities for content compression resistance and for content hugging can be set separately for fine-grained control over when which constraint breaks.
We can use this knowledge to let the parentView's height shrink if a child is hidden. We need a "rubber band constraint" for the parent's height:
Constrain the height of the parent to zero with a low priority, say 2.
Whenever you hide a view, make sure to lower the vertical compression resistance priority of that view to a value less than the rubber band constraint priority, say 1. Now the rubber band overpowers the compression resistance constraint, causing the height of that view to collapse and the parent to shrink accordingly. Be sure to raise that priority to a value greater than the rubber band constraint when un-hiding the view to reverse the effect.
Now i am trying to set autolayouts so taht the mainView height is depended on its children
You cannot do this by constraints alone. Autolayout does not, in and of itself, normally size a view "from the inside out", i.e. by using its subview constraints. (The exception is for special self-sizing views like a scroll view's container view or a table view self-sizing cell.)
However, you can do it in code. This is what systemLayoutSizeFitting is for. You will have to perform manual layout on the superview, but you can do it easily by calling this method.
Each UI element requires 4 constraints to infer its bounds and position. The x position, y position, height and width.
Assuming you need to shrink the mainView to the height of the UILabels, set all the three constraints except the heightConstarintfor the mainView. ie, set constraints for x, y and width. Now set all the four constraints for the three child views. A constant value must be explicitly set for the heights of all the three subViews. Now the height for the mainView will be inferred from the heights of the child views. To wrap the labels, set the heightConstraint of the UIImageView to zero in code whenever required. An IBOutlet for the heightconstraint of UIImageView can be made to set it to zero.
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.
I'd like to use Auto Layout for my view to have the maximum width in it's superview while maintaining that the width is divisible by 7.
Auto layout doesn't have direct support for divisibility. If you want it divisible by 7 because there are 7 sub-elements that you want equally sized and distributed, then you can set those constraints for the sub-elements and appropriate constraints relating the sub-elements to the containing view and you can achieve that.
If you don't naturally have such sub-elements, you can artificially add them just to achieve your divisible-by-7 requirement. You can add UILayoutGuides (if targeting >=iOS 9.0) or hidden subviews (if targeting
Whether you naturally have such sub-elements or you're adding them just for this purpose, the approach is the same. Set constraints that they all have equal widths. Set constraints to put them in a row (trailing of one equals leading of the next). Set constraints to make their combined width equal to the containing view's width by setting the leading edge of the first equal to the leading edge of the container and the trailing edge of the last equal to the trailing edge of the container.
You will need to set some vertical constraints to dictate their vertical position and size, but those are relevant to this issue.
Now, set the constraints on the container relative to its superview. For example, constraints to center the container in its superview horizontally and vertically, plus a constraint to make the container's leading edge equal the superview's leading edge, but with a non-required priority (such as 900). It has to be non-required because it will sometimes have to be broken to maintain the centering and the divisible-by-7 width implied by the internal constraints. But, even when it has to be broken, the auto layout system will try to get it as close as possible, which is what makes the container as wide as possible.
You will also need a constraint that the leading edge of the container is greater than or equal to the leading edge of the superview, at required priority. That's because otherwise the auto layout system may make the container slightly wider than the superview if that's the closest it can get to being exactly as wide as the superview while maintaining the divisible-by-7 width.