In one of my view controllers I have a square control that I want resized based on the user's device. The app will always run in portrait. I have an aspect ratio constraint on the control to keep it square.
I have the control embedded in a container UIView. The container has constraints attaching it to the left and right edges of the content view and constraints spacing it from other controls above and below the container. Depending on the iPhone model, the container view can have longer top/bottom sides than left/right sides (iPhone 4), or it can have longer left/right sides than top/bottom sides (iPhone 6). I want the square control to be horizontally centered, pinned to the top of the container, and I want it to expand to fill the container view, yet keep it's aspect.
As far as I can tell, I cannot pin the edges or some constraints will break, yet when I only pin the top, the control shrinks to nothing. I have played around with compression resistance on the control but that had no effect.
If there's a solution without the need of the container view, that's fine. It just seemed like it would be easier to control with the container.
Situations like this can often be solved by pairing an equality constraints at a reduced priority with an inequality at required priority.
So, for example, a constraint that the square's leading edge equal its superview's leading edge at priority 900 and a constraint that the square's bottom edge is less than or equal to its superview's bottom at required priority.
When the superview is taller than it is wide, the leading edges will be able to coincide, so that constraint will be satisfied and the bottom will be less than the superview's bottom, so that constraint will be satisfied. When the superview is shorter than it is wide, the square will not be able to grow once its bottom coincides with the superview's bottom, because that's a required constraint. The constraint for the leading edge will be broken, which is OK because it's not required, but the auto layout system will still try to get as close as possible, which is what makes the square as large as will fit.
By the way, compression-resistance and content-hugging priorities are only relevant for view's which have intrinsic size and only related to that intrinsic size. A container UIView has no intrinsic size, so those priorities are irrelevant.
Related
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 have a UILabel (the subtitle) that I want to have a static X origin, but extend to the edge of its nearest neighbor. There's a button ("Visit Link") that is optionally removed from the superview at runtime if not needed. The constraint from the label to the button has a priority of 1000, and the constraint from the label to the superview container has a priority of 250:
However, when I run the application removing the button (via .removeFromSuperview() in the viewDidLoad method), via the view debugging I see that the content size is setting the width of the label, taking priority over the constraint I have set.
I expect the label to extend to the edge of the view, but as you can see, the constraint is greyed out - I assume trumped by the (content size) constraint instead:
Does the (content size) constraint have a higher priority than my Trailing Space to: Superview constraint? And how can I change it, since it's not a constraint I've even defined?
When you remove the button from the view hierarchy, that also removes any constraints involving the button. So, all that's left is the trailing constraint to the superview at priority 250.
The label has an intrinsic width based on its content. That means that its horizontal content hugging and compression resistance priorities come into play. Its content hugging priority is 251.
That means that it's more important to the auto layout system that the view's width be no larger than necessary than it is to keep its trailing edge at 8 points from the superview's trailing edge.
You should probably increase the priority of the trailing constraint. You want it to be less than the trailing constraint to the button so that, in the case where the button is present, it doesn't conflict. You also want it less than the button's compression resistance priority, so that the button doesn't get squished to allow the label to be 8 points from the superview. But, other than that, you want it to be as high as possible. (In the hypothetical case where you simply got rid of the possibility of there being a button, you would normally make that trailing constraint required, right? So, it should be as close as possible to required without causing undesirable side effects.)
If you're targeting deployment to iOS 9.0 or later, you should consider using a UIStackView for this layout. It will take care of some things for you, like adding or removing the appropriate constraints when the button is hidden or shown.
The (content size) constraint, automatically installed by the system at runtime, seems to have a priority somewhere between 250 and 750. When I use 250 or 251 for my Trailing Space constraint, it does not work.
However, bumping the priority of my Trailing Space constraint up to the Xcode-titled High priority of 750, allows it to take precedence. So the defaults for the width of UILabel seem to fall "somewhere in the middle."
Autolayout, you silly.
From the second screenshot, it looks like everything is working as it is supposed to.
Two things I'd like to mention:
Always try to set up your layout so you have as few constraints as possible. Each constraint adds complexity.
Yes, content size of the label does indeed have a higher priority. That's why the label is not resized to the right-most edge, which is good. What you are seeing is the label's intrinsic size which means that UIKit knows how big the label is supposed to be drawn, depending on font, text, etc.
To make this layout a bit more robust, I'd change the Trailing space of the label so that it has priority 1000 but is >= 0.
This way, the layout will be valid with or without the Visit Link button and the content size of the label (it's intrinsic size) will resize it to whatever length it needs to be, but no more than the right edge of its superview.
Hope this helps!
UPDATE: Wrote a quick post on why this content size is appearing and why you should use it to your advantage.
I have placed my content view inside scroll view. Also I have set the equal width constraint for content view and main view. Even then the content view has its size based on its sub views.
Screenshot:
There is a space after the content view. (content view background is yellow). This is only with iphone 4s simulator
I think the problem is that the subviews of your content view are not flexible enough horizontally. Since your layout needs to work on all iPhones, the content will be much wider on the iPhone 6s than the iPhone 4s.
For instance, if your "NO" image width has an absolute value, and it has an absolute leading distance to the content view and absolute trailing distance to the content view, then the content view will be over constrained. You have competing constraints telling the content view how wide it should be: 1) equal to the width of the screen or 2) equal to the width of the contained image plus its distances from the sides of the screen. With too many constraints on your content view size, Auto Layout may not choose the constraints you'd like and you can end up with the content view not sized as you would like or expect.
Instead, you should either:
Allow the image to float: Define the width of the image and then center it in the content view. This will allow the distances to the sides to expand on larger devices. OR
Allow the image to grow: Set the leading and trailing edge constraints to constant distances, but do not set a width for the image. This will allow the image to stretch on larger devices. Set an aspect ratio constraint to allow the image height to grow proportionally with the image width to maintain the proportions. Be sure to remove any other height constraints on the image if you set the aspect ratio or your image might will be overly constrained and not grow.
Repeat this with the other subviews in your content view, making sure that they either float or grow horizontally in the content view.
You might need to treat the vertical size of your content view differently. In the vertical direction, you probably haven't constrained the height of the content view, so then you must make sure that the subviews have explicit heights (or are constrained in some other way like with an aspect ratio constraint), the subviews should be connected to each other in a chain from the top of the screen to the bottom of the screen with absolute distances. The top most subview should have a vertical distance to the top of the content view. The bottom most subview should have a vertical distance to the bottom of the content view. This will give Auto Layout enough information to calculate the height of your content view.
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.
Suppose that I have the following view controller and this is how I want to see it on all iPhone:
If I run it on iPhone 6 it has the following look:
Here you can notice that UITableView not fit the whole screen and UIImageView doesn't placed at the bottom of the screen.
How can I achieve the required behavior via constraints in XCode 6? I thought that I need the following constraints:
Leading space and top space to container margin for UITableView
Bottom space and trailing space to container margin for UIImageView
Vertical Spacing between UITableView and UIImageView
But it doesn't work as expected even after auto-resolve constraints issues:
Thanks in advance.
Ok, a few things here:
Each view needs enough constraints to define it's x and y position, and it's width and height unambiguously. To start with, go back to Interface builder and delete all of your constraints and lay out the view as you would like it to look. You want to have control over every constraint added, don't let IB automatically resolve the issues, as in all likely hood it won't do what you want.
Do you have an image that is the size you want it to be on screen, once you've factored in #2x, #3x etc? If so, then your job will be easier, as the width and height of the image view can be defined by the width and height of the image (ie the image view's intrinsic content size).
In order to use Autolayout effectively, you need to think about your view holistically, and think about how you want your views to behave when the screen size changes, be clear in your head about the behaviour.
To achieve the layout you want, I would do the following:
Constrain the tableview's leading, top and trailing edges to the superview, with a constant value of 0. This means it can get wider and thinner with the device, it will stretch horizontally, but always stick to the top. This has defined the tableview's x and y position, as well as it's width (height still to go, but keep reading...)
Constrain the image view to match the horizontal centre of it's superview (x position defined) and constrain it's bottom edge to the superviews bottom edge (y position defined). If've you've got the right sized asset, then that will take care of the width and height too. If not, you could go ahead give it explicit width and height constraints.
Now we can constrain the tableview's bottom edge to the top of the image view, with a constant of 0 (ie touching). Note we haven't give the table view an explicit height constraint, so as the device screen grows vertically, the table view will stretch vertically.
Autolayout is hard at first. I'd recommended lots of reading to get over the initial hump, really get to know what a constraint is doing, it's limitations, and the way in which the system parses constraints to translate them into frames. This book is really good, and really helped me learn:
http://www.amazon.co.uk/Auto-Layout-Demystified-Mobile-Programming/dp/0321967194
Best of luck
First make sure you have selected the correct size class. The 'Compact Width | Regular Height' size class must be selected in the Interface Builder. Now add the Trailing space,Leading Space, Top space and Bottom space constraints to the table view. For the image view set the view mode to Aspect fit and add the constraints : Align Center Y ,Top space,Bottom space, Leading space, Trailing space and Aspect Ratio .