ChatGPT解决这个技术问题 Extra ChatGPT

How can I set aspect ratio constraints programmatically in iOS?

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?


E
Eugene Brusov

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:

https://i.stack.imgur.com/cTqM1.png


Great answer. This is actually not 5:1, but 1:5 aspect ratio :)
M
Mahesh Agrawal

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))

Its giving me this error Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Multiplier is not finite! That's illegal. multiplier:nan'
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.
it will take height:width = 1:2 that is 0.5.
Don't forget to add this line before setting any constraints programatically: [self.yourview setTranslatesAutoresizingMaskIntoConstraints:NO];
When using constraints, my frame sizes were all zero. To get the aspect ratio, instead, I used self.yourview.intrinsicContentSize
E
Elijah

Swift 3:

yourView.addConstraint(NSLayoutConstraint(item: yourView,
                                          attribute: .height,
                                          relatedBy: .equal,
                                          toItem: yourView,
                                          attribute: .width,
                                          multiplier: 9.0 / 16.0,
                                          constant: 0))

For iOS 8 and above you should be using the isActive property of NSLayoutConstraint instead of calling addConstraint(:). aspectRatioConstraint.isActive = true
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.
Z
Zanael

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

https://i.stack.imgur.com/fqhSi.png

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]];

P
Pankaj Kulkarni

Helper Extension on UIView

extension UIView {

    func aspectRation(_ ratio: CGFloat) -> NSLayoutConstraint {

        return NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: self, attribute: .width, multiplier: ratio, constant: 0)
    }
}

And usage is:

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


and usage: view.aspectRation(1.0/1.0)
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.
t
tuandapen

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()

You should always deactivate (potentially conflicting) constraints before adding new ones to avoid autolayout errors
A
Alessandro Francucci

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).activate()