I'm confused about how Cocoa's Autolayout determines whether a layout is ambiguous. Here's a simple example:
The observed behavior is as follows. The spacers to the left and right of the green rectangle are always the same width. When you stretch the superview horizontally outwards, the spacers stick to 80 while the rectangle expands. When you shrink the superview horizontally, the rectangle sticks to 398 while the spacers shrink to 10, after which the rectangle continues to shrink. At no point is the layout labeled ambiguous by IB.
However, you'll notice that the horizontal layout is defined almost entirely by inequalities! From what I can see, when the rectangle has a width of > 398, there's no reason for the spacers to have a width of 80. They could have a width of anywhere from 10 to 80 and still satisfy each horizontal inequality. That sounds ambiguous to me, but IB clearly does not agree.
There must be some implicit rule I'm missing. Please help me out!
Auto layout has, coincidentally, ambiguous documentation. Therefore, the behavior you observed from your application is technically undefined. Here is exactly what the documentation says:
Constraints have a priority level. Constraints with higher priority levels are satisfied before constraints with lower priority levels. The default priority level is required (NSLayoutPriorityRequired), which means that the constraint must be satisfied exactly. The layout system gets as close as it can to satisfying an optional constraint, even if it cannot completely achieve it.
The requirements are therefore
The layout system gets as close as it can to satisfying an optional constraint
Constraints with a priority of 1000 must be satisfied exactly
Higher priority constraints are satisfied before constraints with lower priority levels
However, as you have noticed
What causes the spacer to greedily expand to 80, versus the green rect expanding maximally on account of it having a higher priority?
Auto layout does not define "close as it can" and "Higher priory constraints are satisfied before constraints" in a unambiguous manner. The Auto layout documentation mentions these two dimensions: priority and closeness; but these documents do not say how the optimization of these dimensions interact.
With that being said, here is one theory of how auto layout could operate based on the fact that auto layout is probably a multi-variable linear decomposition solver.
Let's consider your corner cases
When you stretch the superview horizontally outwards the spacers stick to 80 while the rectangle expands
When you shrink the superview horizontally, the rectangle sticks to 398 while the spacers shrink to 10
after which the rectangle continues to shrink
These corner cases can be reduced to
Case A - superview > 398+80+80 (Spacers fixed at 80, rectangle expands from 398)
Case B - superview > 398+10+10 (Rectangle fixed at 398, spacers contract from 80 to 10)
Case C - superview <= 398+10+10 (Spacers fixed at 10, rectangle contracts from 398)
Notice how the numbers never jump from case to case. There is a smooth connection from Case A to Case B in the spacers, going from 80 -> 80 at the critical points. The same occurs for the rectangle from Case B to Case C. The algorithm used by auto-layout always results in a smooth solution when dealing with critical points.
Related
I have 3 UIStackViews laid as follows edge to edge:
SV1 ---- SV2 ---- SV3
SV1 sits at a fixed distance of 5 points to superview leading, SV2 center is aligned with center of superview, and SV3 is at a fixed distance of 5 points to views trailing. Problem is on iPhone SE, SV2 is too wide and too close to SV1 and SV3. How do I set autolayout constraints so that SV2 is at a minimum distance D to SV1 and SV3?
You can create two constraints: first between sv2 and sv1 and second between sv2 and sv3. The trick is to set the constraint not equals to a a value but greater than.
You can create horizontal spacing constraints between the stack views and set them to greater-or-equal. This takes care of the minimum space between them.
But since they were to close together this means at least one of them has to get smaller to make space for the separation. You control which one by setting the content compression resistance priority of the views inside your stack views. The one with the lowest value shrinks.
If all three stack views have different widths you also could lower the priority of the center constraint to a value below the spacing constraints priority. That would mean that the center view is pushed to one side to make room for the spacing.
There are lots of possibilities that depend on your exact views. The best way is to experiment a bit with the priorities.
I want to be able to create a UILabel that will adjust it's size in a more complex way than normal AutoLayout. I've looked over the internet for an answer to this, but to no avail.
Here's what I want it to do.
Say you're viewing it on an iPhone. I want it to be 16 points away from either edge centered in the middle. (Height does not matter in any of this)
However, when the screen gets wider, I want the UILabel to stretch so that it's 16 points away form each edge UNTIL it reaches, say, 500 width. Once it reaches 500 width, I don't want it to get any wider. This is where the 16 points on either side increases, still keeping the label in the center.
Now you're viewing it on an iPad landscape. The UILabel is exactly 500 points wide and in the center.
If possible, I would like to be able to accomplish this using AutoLayout, and not code, but if code is a must, I can deal with that.
Thank you for your consideration. All help is appreciated.
You can do this with 3 constraints:
center the label horizontally in the view
set a width constraint of <= 500
set a leading space constraint of 16. Give this a priority of < 1000.
When the view is wide (like on an iPad), the label will stretch to its full width of 500. Auto Layout will keep the label centered, and it will try its best to satisfy the 3rd constraint by keeping the the leading space as close to 16 as possible. It chooses to break this constraint because the priority is less than 1000.
When the view is narrow (like on an iPhone), the label will have a leading space of 16 (and trailing space of 16 because the label is centered). The width will be whatever is left, because that satisfies the width <= 500 constraint.
I have a problem with multiplier and cannot understand how this feature works. For example i have view has 6:1 multiplier(To SuperView.Leading) as below.
When i change the multiplier to 2:1 it seems like below picture.
My question is in the 6:1 relation what does 6 and 1 mean. And also in 2:1 relation what does 2 and 1 mean. Similar consider you have three view like the picture below. Totally there 4 blank areas between subViews and superView. How can i say every blank area must be the SuperView.Width / 6 (and every blank width must be equal)
Thanks in advance.
When working with autolayout, especially when you are working with proportional layouts, you have to use multiplier.
I have to explain here some mathematics.
We know straight line equation.
Y = Mx + C
In above equation. Assume M is your multiplier and C is your Constant.
Thus suppose you have superview (in case of iphone 6s plus) of
414(width) x 736(height) size. On that view suppose you created subview.
Now if you want subview size exacly half of superview size, then just drag two constraints from subview to superview. (i.e. Equal Width and Equal Height)
See this Image
Obviously now you will get an error. just like I'm getting. (See below Image)
Now click on both of the constraints one by one, and use multiplier as 0.5. Then use above straight line equation.
Here 0.5 means you want width of subview = superviewWidth / 2.0 i.e. 207 px.
In other words you can provide multiplier as 207:414 also.
Y i.e. subviewWidth = ((M i.e. 0.5) * (x i.e. 414 i.e. superviewWidth)) + (Constant i.e. Zero)
Finally you get subviewWidth = 207 px
Similarly do for height of subview. Provide multiplier 0.5 or 368:736.
When done all things, don't forget to click on subview and update frames.
This way constants and multiplier will works.
Multiplier is there for creating Proportional Constraint. Auto Layout calculates the first item’s attribute to be the product of the second item’s attribute and this multiplier. Any value other than 1 creates a proportional constraint.
In your case, 6:1 means multiplier is 6/1 = 6. Which means
view.leading = Superview.leadingMargin*6
replace : with / - you will understand what it means.
In my example multiplier is 1:2 = 0.5
height red view is 0.5 times greater than the superview
When it comes to the multiplier it depends on what constraints you are dealing with. You have the views leading constraint connected to the superview leading margin. When the constant is 0 that gives you an 8 points gap. When you change the multiplier you will be effecting that gap. That's why when you do 2:1 you see it go to the right 8 points. Essentially multiplying the original 8 point gap by 2. If you do 1:2 it will go from 8 points to 4 points, essentially dividing the original 8 point margin by 2.
Now when you look at Adrians example, he only multiplied it by 1:2 so how did that make it half of the entire screen? That's because he did that on the height constraint. The view was originally the full size of the superview, but when he multiplied it by 1:2, he indicated that he wanted it to be 1/2 of its original height. Giving you that end result.
So the important thing to understand is that multiplier may seem to act different depending on the situation but that's because it depends on what constraints you are dealing with.
here is a good answer that goes into this more:
Understanding multiplier in auto layout to use relative positioning
the link details how if you wanted to make the leading edge 10% and trailing edge 90% you would need to set both constraints in relation to the trailing edge.
Multiplying the trailing constraint by 0.9 and the leading constraints by 0.1.
In regards to your question about the equally separated views, you should use a stack view. They were made for situations like this so you didn't have to deal with all the constraints. You just need to set constraints for the stack view and configure it accordingly.
In my app for iOS 8, I have a UISegmentedControl that stretches to fit the width of the device's screen. So on an iPad it's more pixels wide than it is on an iPhone 6+, which is more pixels wide than the iPhone 6, etc.
Centered just beneath each segment of the UISegmentedControl, I have a UILabel. So there are 5 segments and 5 UILabels. Each UILabel has a fixed width (fixed by constraint). However if the display size increases they become uncentered.
How in Interface Builder can I specify a constraint that will force each UILabel to become centered beneath each segment? I would be happy if I could just get the elements to remain proportionally spaced with each other as the display size scales, but I can't figure out how to do that, either.
All I can seemingly do is to center the middle UILabel directly under the middle segment by specifying a Center X Alignment between that and the UISegmentedControl.
I specified a Horizontal Space constraint between all the UILabels, and between the outer UILabels and the edges of the view, and set all these to "greater than or equals". They all have the same priority, but strangely, they don't all scale proportionally to each other.
The resulting problem is that the amount of Horizontal Space between each of the UILabels does not scale smoothly as the width of the device's screen increases. If I align everything to be in the proper positions on the iPhone 5S width of screen, then on the iPad their alignment is all wonky, and only the middle one lines up with its segment. The rest of them are all off center.
It appears that there is no way to specify a percentage of the over-all display width as a constraint -- you can only specify things in terms of pixels. Really?!?!
Clearly I could make the width of the objects to be flexible, but because they are text labels with right-aligned text, that screws everything up.
Surely I'm missing something here... since the point of Auto Layout is to make your interface scale according to the screen size, surely there is a way to specify a constraint as a percentage of any given view or subview... surely!!! But how? I've read the documentation and I cannot, for the life of me, figure it out.
BTW I did see that in the past, people have used crude hacks like spacer views or multiple sets of constraints, but surely those are outdated answers, and I'm just overlooking something extraordinarily obvious... right?
You can do this by making the centerX constraint of your labels equal to the superview.trailing times 0.1, 0.3, 0.5, 0.7, and 0.9 with constants of 0. To make these constraints, add your 5 labels to the view. Give the left most one a vertical spacing constraint to the segmented control. Select all 5 labels and give give them a "vertical centers" alignment constraint. Now control-drag from each label to the right side of the screen, and select the "Trailing space to container margin" constraint. Edit each one of these trailing constraints to look like this (except for the multiplier that needs to be given the values I mentioned above):
You'll have to reverse the first and second item (which you do from the pull down on the first item), change the Label.trailing to Label.Center X, and uncheck the "relative to margin" box, then correct the constant and multiplier values.
This approach will only work if the segmented control stretches all the way across the screen with no padding to the edges. If you want padding to the edges, then you need to use a completely different approach. You would need to create 5 UIViews below your segmented control -- align the left edge of the left-most one to the left edge of the segmented control. Align the right edge of the right-most one to the right edge of the segmented control. Give the 5 views equal width, and 0 length horizontal spacing constraint from each to its neighbor. This will give you 5 views that mimic the segmented control in width, with each view being the same width as one of the segments (assuming all the segments are the same width -- if that's not the case, you're screwed). Then you only need to add your labels as subviews of these 5 views, and give them centerX and centerY constraints.
I have a UIButton and a UILabel constrained to be a standard distance from the bottom of the Superview. Works well on the iPad, but on the smaller iPhone screens, when other elements take up too much space, these views are pushed off the edge of the screen despite their constraint to remain a standard distance from the bottom. Why is this so?
What I would like to have happen is for the four rectangles to shrink in size so that there is still room for the "Go Back" and "Question" label to remain a standard distance from the bottom. The four rectangles can maintain aspect ratio and equal width/height by all shrinking at the same ratio. I have no constraints on their needing to be equal to or larger than a certain size. I've tried lowering their Content Compression Resistance Priority as well.
Configuration:
(I have also tried "equal" and "<=" in top spacing between "Go Back" & bottom left rectangle)
("Greater than or equal" works best on iPad to keep "Go Back" at the bottom of the screen)
How it looks on iPhone 6 and iPhone 6+ - with the labels cut off at bottom:
Did you try to lower those four buttons' height constraint's priority??
For example like this, try to set them to 750
You can make this work with a couple of changes and additions. Give the leading and trailing constraints between the top 2 rectangles and the superview a lower priority (I used 749), but still keep them as "equal". This will keep them at the standard distance from the edges if it's possible, but will allow them to have a larger spacing if the vertical space combined with the aspect ratio requires it. The problem with this, is that since they aren't required any more, when those constraints need to stretch, there's nothing that says they have to stretch equally; therefore, we need some way to keep the rectangles centered. So, instead of a spacing constraint between the left and right top rectangles, add a small view (I used 8x8) that has a centerY constraint to one of the rectangles, and zero constant spacing constraints to the two rectangles. Give this view a centerX constraint to the superview; this construct will give you the same spacing between your rectangles that you had before, but will keep them centered in the superview while allowing them to shrink in width (and height to keep the aspect ratio) if need to accommodate the vertical space.