简体   繁体   中英

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM