Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Percentage based margin using autolayout

Tags:

ios

autolayout

I'm trying to place my subview with a left margin based on the width of the parent view. This sounds simple but I can't figure out how to do it using autolayout.

Logically, I would only need to set the left margin value at a certain percentage value of the parent's width but at the moment, I fail to translate that logic to autolayout.

This is my code at the moment:

var view = UIView();
view.backgroundColor = UIColor.redColor();
view.frame = CGRectMake(0, 0, 320, 400);

var sview = UIView();
sview.setTranslatesAutoresizingMaskIntoConstraints(false);
sview.backgroundColor = UIColor.yellowColor();
//sview.frame = CGRectMake(0, 0, 50, 50);
view.addSubview(sview);

var dict = Dictionary<String, UIView>()
dict["box"] = sview;

var con1 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 20.0);
view.addConstraint(con1);

var con2 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Width, multiplier: 0.75, constant: 0.0);
view.addConstraint(con2);

var con3 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Width, multiplier: 1.0, constant: 0.0);
view.addConstraint(con3);

var con4 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Height, multiplier: 1.0, constant: 0.0);
view.addConstraint(con4);

This is the where the code returns an error:

var con2 = NSLayoutConstraint(item: sview, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Width, multiplier: 0.75, constant: 0.0);
view.addConstraint(con2);

Error:

* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* +[NSLayoutConstraint constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:]: Invalid pairing of layout attributes'

Does anyone have any idea on how to achieve this? I just want the left margin to be 0.75% of the parent view's width.

Thanks.

like image 577
Sani Avatar asked Nov 28 '14 09:11

Sani


People also ask

What is Autolayout?

Auto Layout defines your user interface using a series of constraints. Constraints typically represent a relationship between two views. Auto Layout then calculates the size and location of each view based on these constraints. This produces layouts that dynamically respond to both internal and external changes.

What is multiplier in Autolayout?

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.

What is constraints to margin in Xcode?

The “Constrain to margins” checkbox determines whether constraints to the superview use the superview's margins or its edges. The lower portion of the popover lets you set the item's width or height. The Width and Height constraints default to the current canvas size, though you can type in different values.


3 Answers

What you want is the left of sview to be at some point of the left of view and you are writing that you want the left of sview to be at some point of the width of view which is not a correct pairing of layout attributes as your error says.

Here is what you need to do:

NSLayoutConstraint(item: sview, 
              attribute: NSLayoutAttribute.Left, 
              relatedBy: NSLayoutRelation.Equal, 
                 toItem: view, 
              attribute: NSLayoutAttribute.Left, 
             multiplier: 1, 
               constant: (CGRectGetWidth(view.bounds) * 0.75));

Hope it helps!

EDIT

I found a great article about Percented based margins:
https://web.archive.org/web/20170624134422/http://simblestudios.com/blog/development/percentage-width-in-autolayout.html
Or even simpler:
https://web.archive.org/web/20170704113819/http://simblestudios.com/blog/development/easier-percentage-width-in-autolayout.html

enter image description here

like image 143
Kevin Hirsch Avatar answered Oct 04 '22 20:10

Kevin Hirsch


It is possible to create a percentage-based margins with auto layout constraints between a subview and its superview. The margin will change dynamically as the size of the superview changes. For example, this is how to create a trailing 10% margin.

  1. Create a trailing constraint between your view and its superview.
  2. Make sure the first constraint item is the subview and the second item is the superview. This can be done by clicking on the first item drop down and selecting Reverse First and Second Item.
  3. Change the constant value of the constraint to zero in the attributes inspector.
  4. Change the multiplier value to 0.9.

enter image description here

The is one problem with this manual approach though - it does not work in right-to-left language, Arabic, for example. Right-to-left layouts require a bit different settings for the constraint but we can not keep both in one storyboard.

Here is a library that I wrote that lets you create percentage-based constraints. It does handle the right-to-left language case.

https://github.com/exchangegroup/PercentageMargin

like image 26
Evgenii Avatar answered Oct 04 '22 22:10

Evgenii


You can subclass NSLayoutConstraint to accept a margin percentage via @IBInspectable. Then subscribe to UIDeviceOrientationDidChangeNotification to run the calculation and stuff into the constant value so it is updated whenever the layout changes.

/// Layout constraint to calculate size based on multiplier.
class PercentLayoutConstraint: NSLayoutConstraint {

    @IBInspectable var marginPercent: CGFloat = 0

    var screenSize: (width: CGFloat, height: CGFloat) {
        return (UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height)
    }

    override func awakeFromNib() {
        super.awakeFromNib()

        guard marginPercent > 0 else { return }
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: #selector(layoutDidChange),
            name: UIDeviceOrientationDidChangeNotification,
            object: nil)
    }

    /**
     Re-calculate constant based on orientation and percentage.
     */
    func layoutDidChange() {
        guard marginPercent > 0 else { return }

        switch firstAttribute {
        case .Top, .TopMargin, .Bottom, .BottomMargin:
            constant = screenSize.height * marginPercent
        case .Leading, .LeadingMargin, .Trailing, .TrailingMargin:
            constant = screenSize.width * marginPercent
        default: break
        }
    }

    deinit {
        guard marginPercent > 0 else { return }
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

First you specify the new subclass in Identity Inspector: enter image description here

Then you can use it like this: enter image description here

The only caveat I can think of is the constants in the Storyboard are not used at runtime, but instead are overwritten with the percentage based calculation. So it does require some duplicate effort, once to actually layout the views on Storyboard based on points just so you get a sense of what the screen layout looks like, then percentages kick in at runtime.

For more details, check out this article: http://basememara.com/percentage-based-margin-using-autolayout-storyboard/

like image 31
Basem Avatar answered Oct 04 '22 22:10

Basem