How do I solve this Auto Layout "puzzle" with constraints? - ios

On the left is the normal state of the layout. On the right is the expanded state of the layout.
My problem is that I can't figure out how to keep the pink boxes centered within their cells, and the green lines connected between the pink boxes as the layout grows in any direction.
The AutoSizing.xib with just these two views can be found here: https://dl.dropboxusercontent.com/u/6979623/AutoSizing.xib

There are two approaches to keeping the pink boxes centered within the four quadrants of the super view:
You can add four invisible views that even split up the four quadrants of this main view (using constraints to pin them to their respective corners and another set of constraints to make them equal widths and heights). If I were going to represent that in VFL (just because it's a concise way of representing all of those constraints), it might be something like:
H:|[quadrant1][quadrant2(==quadrant1)]|
H:|[quadrant3(==quadrant1)][quadrant4(==quadrant1)]|
V:|[quadrant1][quadrant3(==quadrant1)]|
V:|[quadrant2(==quadrant1)][quadrant4(==quadrant1)]|
You can then center the pink views in the center of each of those four quadrant views.
You can add center constraints that employ multipliers to offset these pink squares within their shared superview. Interface Builder has only recently gotten multiplier support and it's not super solid, in my opinion, so when I want to use multipliers, I often fall back to adding those programmatically.
Once you have the pink boxes laid out correctly, then the green lines between them are simply fixed spacing leading/trailing or top/bottom for horizontal or vertical lines, respectively. Combine that with fixed width and center alignment and you're done. Bottom line, take care of the placement of the pink boxes, as discussed above, and then the creation of the green lines becomes pretty easy.

Related

Autolayout constraint to a point at a _percentage_ of a view's width?

Consider the following simple diagram:
Our goal is to align some arbitrary point along the width of the yellow view with the horizontal center of the black view. The position and size of the black view, and the size of the yellow view, are unknowns; they can change depending on the circumstances. (In real life, the yellow view is a label whose size depends on its contents, which are dynamic.)
Now then:
I know how to align the leading anchor of the yellow view with the horizontal center of the black view.
I know how to align the trailing anchor of the yellow view with the horizontal center of the black view.
I know how to align the horizontal center of the yellow view with the horizontal center of the black view.
But that's not what I want to do. I want to be able pick an arbitrary percentage of the yellow view's width — say, as in the diagram, about 25% — and align that point with the horizontal center of the black view. This percentage can change in real time, depending on other circumstances.
My first impulse was to tie the yellow view's horizontal center to the black view's horizontal center and give the constraint a multiplier; but that just resulted in a compile error.
So I have to come up with a workaround. Here are some solutions that occurred to me:
Probably the most obvious solution is to add to the yellow view an invisible subview pinned to the leading edge of the yellow view, and whose width is set as a percentage of the yellow view's width. Then the desired constraint is to the invisible view's trailing edge, and (as a mathematician would say) we have reduced the problem to a previously solved problem. (I call this a "spacer" view.)
A less commonly known but actually quite easy solution is to use a layout guide attached to the yellow view. This works exactly like the invisible spacer subview, but it isn't a subview and so we avoid the extra overhead of an unwanted view.
Those solutions are very nice, but is there a solution using just a constraint? It seems I cannot translate the concept "the width of the yellow view", or "a percentage of the width of the yellow view", into a value that I can tie a constraint to.
It seems I cannot translate the concept "the width of the yellow view", or "a percentage of the width of the yellow view", into a value that I can tie a constraint to.
Actually, yes you can. There is an incredibly clever and obscure solution built into UIKit and autolayout. (It is sufficiently obscure that I didn't know about it until yesterday, even though I've been doing this sort of thing ever since autolayout was invented.)
Dimensional values in autolayout, such as a view's widthAnchor, are NSLayoutDimension objects. It turns out that you can form a "dynamic" NSLayoutDimension anchor yourself, using the anchorWithOffset method:
https://developer.apple.com/documentation/uikit/nslayoutxaxisanchor/2866024-anchorwithoffset
So, in the example in my question, let the yellow view be called movableView and let the black view be called fixedView. Then we can say:
let anchor = movableView.leadingAnchor
.anchorWithOffset(to: fixedView.centerXAnchor)
What we now have is not a constraint. It's a dimensional anchor, similar to widthAnchor; in particular, it is the horizontal distance between the leading edge of the leading anchor of the yellow view and horizontal center of the black view. The point is that we can now form and activate a constraint upon that anchor. This is a constraint which, like a widthAnchor constraint, describes the anchor's length, and for which, like a widthAnchor constraint, we can supply a multiplier. Here it is:
let multiplier: CGFloat = // value representing the desired percentage
let constraint = anchor
.constraint(equalTo: movableView.widthAnchor, multiplier: multiplier)
Then:
If the multiplier is 0, the yellow view's leading anchor and the black view's center X anchor are directly aligned.
If the multipier is 1, the yellow view's leading anchor and the black view's center X anchor are aligned with an offset equivalent to the yellow view's width. But that is the same as saying that the yellow view's trailing anchor and the black view's center X anchor is aligned!
And so too for any value in between. If the multiplier is 0.5, the horizontal centers of the views are aligned. If the multiplier is 0.25, we get the image shown in the question: the black view's horizontal center is aligned with a point 1/4 of the way along the width of the yellow view.
So if we now activate constraint, we get the desired result.
While writing this Q&A, I was not completely convinced that anchorWithOffset was a better solution than any of my proposed workarounds. Possibly a layout guide was a sufficient solution. Was anchorWithOffset really needed?
But in formulating and reconsidering this answer, I'm more and more convinced of the power and value of this neglected autolayout feature. anchorWithOffset has the advantage of existing in a world of pure one-dimensionality, without even the overhead of a layout guide's two-dimensionality. And unlike a layout guide, it isn't a workaround here; it is the thing itself that I was looking for! An anchorWithOffset allows us to do exactly what the question asks to do: define a certain distance, and then set its size with a constraint that can refer to other sizes and can have a multiplier. It lets us follow one of my favorite rules of programming: SWYM (Say What You Mean).
I do appreciate that this is not the first time anchorWithOffset has been mentioned here. I did find some Stack Overflow answers that propose the use of this method. But they were about equal or otherwise fixed distribution; they didn't describe the use of an arbitrary and variable multiplier in the constraint.

How to lay out UIImageViews based on the height that is left?

I am trying to create this layout, but I am struggeling so badly that I have no words left for it. I have tried for three days but yet I can't create the layout.
I am trying to have the three imageviews in the middle scale if the height of the view changes, but they are not behaving correct. I have successfully manage to get the ratios correct though, but the images either goes behind the blue button, or the blue button suddenly looses its height or the text disappears.
Isn't it possible to have three image views in the middle that take whatever height is left after the title and text + blue button have been laid out and then properly show them with correct ratios?
You can try multiple approaches here:
Size Classes:
Lay out the views for each possible size (probably the easiest but the least flexible
Use Constraints with different priorities:
You can set a max-height for the image views by adding a constraint for the height and then setting its relationship to equal or less than. This allows the image to scale up until a maximum size is reached.
Then add a second constraint between the bottom box and the images. Set the priority of this constraint to 750. This makes the image grow in height, when the bottom view moves down.
Then add another constraint which sets the aspect ratio of the image. If you use the mode of the view to Aspect Fit, the image will now always be scaled up to fit the image view.
To make all images scale proportionally, add constraints for equal width and equal height between them, then set the factor to match the proportions between these images.
The last step is now to vertically align the images properly as you showed in the pictures. To do that, add constraints between the images, which align them by their vertical centers, bottoms, baselines or tops.

How to specify a constraint for a UILabel to remain centered beneath each segment of a UISegmentedControl?

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.

Align multiple images equally in different screen size

I am having troubles aligning multiple images by using auto-layout in xcode.
I tried different settings, but nothing seems to work yet (refer to attached photos).
It would be great to hear some opinions from you guys, either in code or storyboard.
I want the image to be aligned equally in different screen sizes.
Too much space
Last image scaled too much
When Equally width is set
Constraints Setting
You need extra views to achieve this
In the sample picture the red rectangles they are all invisible UIView with constraints:
fixed length leading space to the view on the left(or the superview)
fixed length trailing space to the view on the right(or the superview)
fixed length height
This way it's the invisible views who have different width in different screen size while the size of images between them is fixed.
For left most item you should specify leading space to superview, for right most specify trailing space to superview. For all inner gaps between views specify horizontal spacing and for each neighbor view specify equal width constraint. Additionally I recommend for you to specify Align center Y for all views and set the y position constraint only for one of them

iOS Autolayout: Issues with 2 variable sized boxes and 1 fixed sized box

I have now been bashing my head against this problem for a couple of hours and figured it was time to ask somebody else.
I have 2 views that must be the same size, within these two views there are 2 boxes (green and blue in the pictures below), which are of variable sizes, and a box (pink'ish) that is fixed size.
Here is a sample image:
The green and pink boxes are set to be at the top, and the blue box floats underneath them. The blue box should never be further down than 15pt from the lowest of the other two boxes. This means that if the green box becomes smaller (as seen in the next image), then the blue box should stay 15pt from the pink box.
Lastly, since the cells are fixed height then if the blue box becomes smaller, then it should stay at the other boxes, but leave space below itself to fill out the rest of the view (since it must be as big as the view next to it), as I tried picturing here:
The key point here is that we are working on the smaller view of the two.
(The green and blue boxes are both labels with text that must be at the top of the box.)
The best solution I've come up with is to add:
green.bottom >=15 blue.top
pink.bottom >=15 blue.top
blue.bottom >=15 superview.bottom
But I get an "Inequality Constraint Ambiguity" between them, because inequality is not "good enough".
You need to add two more constraints between the blue view and the green and pink views. The should be,
green.bottom == 15 blue.top priority 900
pink.bottom == 15 blue.top priority 900
Your >= constraints have the default priority of 1000, meaning that they are required. This will ensure that neither view is ever closer than 15 points to the blue view. Adding these new equal constraints with a lower priority, means that the system will try to satisfy them, but it doesn't have to. This will result in the system satisfying which ever of those two equal constraints that it can, without violating the >= constraints.
I'm not exactly sure what constraint you need to the bottom of the view (from the blue view) since I'm unsure what size you want it to be.

Resources