Auto Layout Max Width While Maintaining Divisibility - ios

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.

Related

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.

iOS autolayout disregarding "trailing space" constraint, using width/content size instead?

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.

Expand Square View to Fill Rectangular Superview Using Auto Layout

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.

Autolayout height equal to MAX(multiple view heights)

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.

Using autolayout on iOS how can I specify that a view should take up as much space as possible?

I am using autolayout in iOS to try and build a layout with fluid widths. The visual format for the constraint I am currently using is:
[self.scrollViewContainer addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|-(>=32)-[viewToAdd(<=576)]-(>=32)-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(viewToAdd)
]];
That is to say: I want a minimum of 32px spacing on either side, and I want the viewToAdd to have a maximum width of 576px. This works well except that I want the viewToAdd to use up any available space while still meeting all the constraints. Currently I get the viewToAdd only ever being as wide as its intrinsic content size, and the spacing growing as needed.
Is there a way to specify that the viewToAdd should be as large as possible?
You're going to have to specify additional constraints on the view in order to get it to size the view to fill the remaining available space. Currently, you are setting the view minimums and maximums, but not setting any concrete constraints to give autolayout a more complete solution. In addition to the upper bound for the width, you need to give it 1. a starting point to solve the width for the view by either explicitly giving it a width at a lower priority, or 2. give the spacing on either side some more rigid constraints.
Based on what you've described, you need to constrain your view to give it some more substantial bindings to the superview on either side in order to let the system solve for the width. Since you would like to have the view size itself based on it's container's size, you will need to modify the spacing constraints (option 2 from above). In the above instance, you have specified only a minimum spacing, which would result in any of the following constraint solutions being found as valid by the autolayout engine for a 400pt superview:
|-32pt-[20pt]-------348pt-| <-- autolayout will probably choose this one
|-100pt----[20pt]---280pt-|
|-50pt--[20pt-]-----330pt-|
Which is probably not what you are wanting. Even more still, the width of the view can be anything between 0-576pt, which is also probably not what you're wanting. Since autolayout doesn't know what you want, it's simply using the intrinsicContentSize of the view for concrete sizing constraints. Since you chose 32pt as the spacing, a first step would be to give the spacing constraints some more substantial instructions, namely, telling the system that the spacing should be 32pts between the edges of the view and the superview unless the width of the view is >576pts. You would do this like so in your VFL string:
"H:|-(>=32,==32#900)-[viewToAdd(<=576)]-(>=32,==32#900)-|"
This says: "viewToAdd should have a maximum width of 576pts and have padding between itself and it's superview of 32pts. If the size of the superview grows beyond the maximum width of viewToAdd plus the initial padding of 64pts, the padding on either side should grow in order to continue to solve the constraint set."
This results in the following constraints being correct for a 400pt superview:
|-32pt--[336pt]--32pt-|
If you would like for viewToAdd to remain centered in it's superview when the view grows beyond the maximum, you will have to pass in the option NSLayoutFormatAlignAllCenterX to the options parameter in [NSLayoutConstraint -constraintsWithVisualFormat:]. If you did not have the >=32 constraint set on the padding, or did not set a priority lower than 1000 ("required" constraint priority level) on your padding's ==32 constraint, then your superview would be unable to grow beyond 640pts.
Not sure if this exactly answers the question, but for the ones using Storyboard: I found that you can add a width-constraint and constraints for both the distance to the left and right side of the parent view for example. If you then change the width-constraint to '<=' instead of '=' and set the priority of the distance constraints (both leading and trailing) to 750, the view will max out to the max width, respecting the distance-constraints if the view is too small.

Resources