104

I have used auto layout for my view controllers. I have set the V and H positions in constraints, but I want to know how can I increase my button size when it changes to 5s, 6 and 6 Plus. This is the way I added constraints for the login button:

NSArray *btncon_V=[NSLayoutConstraint constraintsWithVisualFormat:@"V:[btnLogin(40)]" options:0 metrics:nil views:viewsDictionary];
[btnLogin addConstraints:btncon_V];

NSArray *btncon_POS_H=[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-100-[btnLogin]-100-|" options:0 metrics:nil views:viewsDictionary];
[self.view addConstraints:btncon_POS_H];


NSArray *btncon_POS_V=[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-70-[Title]-130-[lblFirst]-0-[lblSecond]-20-[textusername]-10-[txtpassword]-10-[btnLogin]" options:0 metrics:nil views:viewsDictionary];

[self.view addConstraints:btncon_POS_V];

But my problem is that while it manages the left and right side gap, it's getting stretched in iPhone 6 and 6 Plus since the height is fixed. How can I increase the size according to the screen size? I think this might be the aspect ratio, but how can I set the aspect ratio constraint in code?

Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
IRD
  • 1,127
  • 2
  • 8
  • 15

7 Answers7

120

Layout Anchors is the most convenient way to set constraints programmatically.

Say you want to set 5:1 aspect ratio for your button then you should use:

button.heightAnchor.constraint(equalTo: button.widthAnchor, multiplier: 1.0/5.0).isActive = true

Here's the full code:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let button = UIButton(type: .custom)
        button.setTitle("Login", for: .normal)
        button.backgroundColor = UIColor.darkGray

        self.view.addSubview(button)

        button.translatesAutoresizingMaskIntoConstraints = false

        let margins = view.layoutMarginsGuide

        button.leadingAnchor.constraint(equalTo: margins.leadingAnchor, constant: 20.0).isActive = true
        button.trailingAnchor.constraint(equalTo: margins.trailingAnchor, constant: -20.0).isActive = true
        button.bottomAnchor.constraint(equalTo: margins.bottomAnchor, constant: -20.0).isActive = true
        button.heightAnchor.constraint(equalTo: button.widthAnchor, multiplier: 1.0/5.0).isActive = true
    }

}

Here're results achieved with code written above. You can see that button keeps its 5:1 aspect ratio across various devices:

Result view

Eugene Brusov
  • 17,146
  • 6
  • 52
  • 68
90

Like this. Try once.

[self.yourview setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.yourview addConstraint:[NSLayoutConstraint
                                  constraintWithItem:self.yourview
                                  attribute:NSLayoutAttributeHeight
                                  relatedBy:NSLayoutRelationEqual
                                  toItem:self.yourview
                                  attribute:NSLayoutAttributeWidth
                                  multiplier:(self.yourview.frame.size.height / self.yourview.frame.size.width)
                                  constant:0]];

or in the place of (self.yourview.frame.size.height / self.yourview.frame.size.width) you can use any float value. Thanks.

Swift 3.0 -

self.yourview!.translatesAutoresizingMaskIntoConstraints = false
self.yourview!.addConstraint(NSLayoutConstraint(item: self.yourview!,
                                          attribute: NSLayoutAttribute.height,
                                          relatedBy: NSLayoutRelation.equal,
                                          toItem: self.yourview!,
                                          attribute: NSLayoutAttribute.width,
                                          multiplier: self.yourview.frame.size.height / self.yourview.frame.size.width,
                                          constant: 0))
Mahesh Agrawal
  • 3,348
  • 20
  • 34
  • 1
    Its giving me this error Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Multiplier is not finite! That's illegal. multiplier:nan' – IRD Jul 10 '15 at 06:49
  • if you set only this constraint then it will not work. you need to set other alignment constraints also. and you can give a float value in multiplier. like if your view width is 200 and height is 100 then multiplier will be 0.5 – Mahesh Agrawal Jul 10 '15 at 06:53
  • I dont understand how the multiplier works. How to do that? – IRD Jul 10 '15 at 06:57
  • 3
    it means in the scenario like if height is 100 and width is 200 and you given multiplier 0.5 with my code, then if your height will increase to 150 by changing any other constraint then width will automatically increase to 300. But to do this you need to increase one constraint of the view like width or height. – Mahesh Agrawal Jul 10 '15 at 07:01
  • 2
    it will take height:width = 1:2 that is 0.5. – Mahesh Agrawal Jul 10 '15 at 07:02
  • Thanks for the explenation. And is this working for gap too between 2 viewes. If we want to increase the gap between 2 views when it comes to larger screen sizes. is this applicable? – IRD Jul 10 '15 at 07:43
  • yes this is applicable. also i have answered another question regarding this. see http://stackoverflow.com/a/31336272/4030971 – Mahesh Agrawal Jul 11 '15 at 09:30
  • 4
    Don't forget to add this line before setting any constraints programatically: `[self.yourview setTranslatesAutoresizingMaskIntoConstraints:NO];` – atulkhatri Mar 01 '16 at 14:25
  • 2
    When using constraints, my frame sizes were all zero. To get the aspect ratio, instead, I used `self.yourview.intrinsicContentSize` – Phlippie Bosman Feb 07 '17 at 18:54
  • make sure you call [self.view layoutIfNeeded]; to make constraint work if you use this one. – Mahesh Agrawal Feb 08 '17 at 07:47
21

Swift 3:

yourView.addConstraint(NSLayoutConstraint(item: yourView,
                                          attribute: .height,
                                          relatedBy: .equal,
                                          toItem: yourView,
                                          attribute: .width,
                                          multiplier: 9.0 / 16.0,
                                          constant: 0))
Elijah
  • 8,381
  • 2
  • 55
  • 49
RNHTTR
  • 2,235
  • 2
  • 15
  • 30
  • 2
    For iOS 8 and above you should be using the isActive property of NSLayoutConstraint instead of calling addConstraint(:). `aspectRatioConstraint.isActive = true` – David Apr 23 '17 at 10:34
  • 2
    Why don't you use `.width` as the first attribute? The result is `attr2 * multiplier `, the aspect ratio is `width / height`, so `width = height * aspectRatio`, if we put the `.height` to the first attribute and the `aspectRatio` to the `multiplier` then the result is totally reversed. – Kimi Chiu Mar 16 '20 at 14:55
18

You can set "Height constraint" at Design-time in Interface Builder. Just check '"Remove at build time", and it removes when App will running.

enter image description here After that you can add "Aspect Ratio" constraint, for example, in viewDidLoad method.

In Xamain.iOS for "16:9" it's looks like this:

    this.ImageOfTheDay.AddConstraint(NSLayoutConstraint.Create(
            this.ImageOfTheDay,
            NSLayoutAttribute.Height,
            NSLayoutRelation.Equal,
            this.ImageOfTheDay,
            NSLayoutAttribute.Width,
            9.0f / 16.0f,
            0));

Or, as Mahesh Agrawal said:

    [self.ImageOfTheDay addConstraint:[NSLayoutConstraint
                              constraintWithItem:self.ImageOfTheDay
                              attribute:NSLayoutAttributeHeight
                              relatedBy:NSLayoutRelationEqual
                              toItem:self.ImageOfTheDay
                              attribute:NSLayoutAttributeWidth
                              multiplier:(9.0f / 16.0f)
                              constant:0]];
Zanael
  • 740
  • 5
  • 10
18

Helper Extension on UIView

extension UIView {
    
    func aspectRatio(_ ratio: CGFloat) -> NSLayoutConstraint {
        
        return NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: self, attribute: .width, multiplier: ratio, constant: 0)
    }
}

And usage is:

view.aspectRatio(1.0/1.0).isActive = true

frouo
  • 5,087
  • 3
  • 26
  • 29
Michał Ziobro
  • 10,759
  • 11
  • 88
  • 143
  • 1
    and usage: view.aspectRation(1.0/1.0) – Michał Ziobro Mar 27 '19 at 08:36
  • 2
    This is indeed an elegant solution barring couple of types: 1. The toItem parameter is set to `.self` instead of `self` 2. And the usage in comment should be `view.aspectRation(1.0/1.0).isActive = true` I tested this in Xcode 10.2.1 Swift 5. – Pankaj Kulkarni Jun 26 '19 at 14:11
6

Swift 5 solution, Xcode 12.4. Requires ios 9.0+

Simply anchor your height constraint to a certain ratio:

let aspectRatio: CGFloat = 9 / 16 // Example of ratio you want to apply
yourView.heightAnchor.constraint(equalTo: imageView.widthAnchor,
                                 multiplier: aspectRatio).isActive = true
bandejapaisa
  • 26,576
  • 13
  • 94
  • 112
Alessandro Francucci
  • 1,528
  • 17
  • 25
5

I have try all answers above but didn't work, and this is my solution with swift 3:

let newConstraint = NSLayoutConstraint(item: yourView, attribute: .width, relatedBy: .equal, toItem: yourView, attribute: .height, multiplier: 560.0/315.0, constant: 0)
yourView.addConstraint(newConstraint)
NSLayoutConstraint.activate([newConstraint])  
NSLayoutConstraint.deactivate(yourView.constraints)
yourView.layoutIfNeeded()
tuandapen
  • 284
  • 4
  • 6
  • You should always deactivate (potentially conflicting) constraints before adding new ones to avoid autolayout errors – John Dec 20 '19 at 19:01