Understanding multiplier in auto layout to use relative positioning

27,358

Solution 1

There are a couple ways to do this. In the simplest case, you've already almost got it: if you want the horizontal boundaries to be at 10% and 90%, then you need to specify both constraints with respect to the trailing edge of the superview -- so Subview.Trailing locks to Superview.Trailing with a multiplier of 0.9, as you say, but then Subview.Leading also locks to Superview.Trailing, just with a multiplier of 0.1:

enter image description here

(and similarly for top / bottom)

On the other hand, the case you mention at the end is a little more complicated: "specifying a label's top should lie 10% beneath another label's bottom." For that you probably won't be able to use fixed percentage insets like the previous case. Instead, you can create an invisible spacer view between them: add a constraint with Spacer.Height = 0.1 * Superview.Height and then attach it between the two labels. (You can also handle the previous case with these spacer views, but for that case it isn't really necessary.)

Solution 2

In my opinion, "You can't have a negative multiplier I don't think, and I don't believe a value greater than 1 does anything when constant is 0" exposed your comprehending deviation.

The rule underneath the hood is just a linear equation:

FirstItem.Attribute1 = (SecondItem.Attribute2 * Multiplier) + Constant

All measured in points. As you see, multiplier(a property of NSLayoutConstraint) is not the multiplier of constant. Follow the equation, what you don't understand will be clear.

As to your specific example, @Archaeopterasa presented a great solution, another is shown below:

Based on the fact you know how to do bottom and trailing, I suppose you've done these two. Then add another two constraints, the effect will be what you want: set label's width to 80% superview's width set label's height to 80% superview's height

At last, if you want to specify a label's top lie 10% beneath another label's bottom, it seems that you cannot implement it without writing a line of code. You have to use code to set the constant of the NSLayoutConstraint object connecting the FirstItem and the SecondItem after the superview's height is known in runtime.

Firstly, control drag from one label to the other and choose "Vertical Spacing".(Or you can do this in other ways)

Secondly, a referencing outlet is needed:

@IBOutlet weak var tenPercentOfSuperview: NSLayoutConstraint!

Then, do this in a appropriate place(for example, in viewDidLoad())

let heightOfSuperview = self.view.bounds.height
tenPercentOfSuperview.constant = heightOfSuperview * 0.1

Everything is OK now.

If you want to know more about this topic, Apple's document is recommended:https://developer.apple.com/library/ios/recipes/xcode_help-IB_auto_layout/chapters/EditingConstraintAttributesintheAttributesInspector.html

Solution 3

Here's the

INCREDIBLY SIMPLE

way to do it.

Just add 'helper' or "measure' views:

The 'calculation helper' views are yellow in the example.

The general technique is so simple and obvious - it doesn't need any explantion, you can get it from the image.

It's one of those "elephant in the room" things they "don't teach you about" in iOS. But it is used constantly in all layouts.

(Indeed, Apple should have made a special UIView subclass "measures" for exactly this purpose - and indeed, many large teams do just that, with the obvious features.)

enter image description here

Notice icon #1 is centered on the right end of the "View 1/4" helper view.

Notice icon #3 is centered on the left end of the "View 3/4" helper view.

You're done, have a Chardonnay.

Conveniently, in the helper views, just set the multiplier to anything you want, depending on the feel wanted. It's incredibly easy to then change those in your code, use IBInspectable, animate, and so on and on...

enter image description here

Share:
27,358
Jordan H
Author by

Jordan H

*OS Developer

Updated on November 01, 2020

Comments

  • Jordan H
    Jordan H over 3 years

    I am trying to understand how one can utilize Auto Layout to position items relative to other views percentage-wise.

    For example, I recently learned that you can specify a view's bottom should lie 4% higher than its superview's bottom by using this:

    self.view.addConstraint(NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 0.96, constant: 0))
    

    This makes sense to me because a multiplier of 1 would align it right at the view's bottom, so you can decrease that amount by 4 percent by changing the multiplier to 0.96.

    But how can you do the same in the other direction? You want to specify a label's top should begin 4% down from the superview's top. If you use that same line but change the attributes to .Top, that means it would be 4% higher than the superview's top (but it actually doesn't move it off screen). You can't have a negative multiplier I don't think, and I don't believe a value greater than 1 does anything when constant is 0. So how do you get that set up?

    I have the same question for implementing leading and trailing. Trailing is easy. If you want it 10% from the right:

    self.view.addConstraint(NSLayoutConstraint(item: label, attribute: .Trailing, relatedBy: .Equal, toItem: self.view, attribute: .Trailing, multiplier: 0.9, constant: 0))
    

    It makes sense because you dial it back 0.1 or 10% instead of aligning fully at 1.0. But how do you do the same for leading? I thought you might be able to set the label's leading relative to the view's trailing, then set the multiplier to 0.1. In my mind that would mean the label would start at the very far right but then be dialed back 90%, therefore obtaining the desired 10% from the left. But that's not the case, I'm not sure why.

    Can you explain how this is works, how to properly use multiplier to obtain these relative layouts?

    To make it easy, let's say you'd like to create a label that has top and bottom 10% of the superview's height, and trailing and leading 10% of the superview's width. On an iPhone in portrait there's going to be more padding above and below the label than there is padding to the left and right of it, like so (yes it's drawn to scale):
    enter image description here

    But let's say on the iPad it's going to be shown in a view that's a perfect square. Therefore the padding will be the same amount all around, like so:
    enter image description here

    The question is how do you define such constraints to be dynamic in value, as opposed to setting a fixed value for a constant. I already know how to do bottom and trailing, but top and leading has me stumped. I'm hoping to understand how to use multiplier to do more advanced layouts, for example, specifying a label's top should lie 10% beneath another label's bottom, as opposed to setting it to a fixed constant.