Search code examples
autolayoutnslayoutanchor

How to set the NSLayoutAnchor to generate an centered subview with aspect ratio 1:1


It doesn't sound too difficult, but after hours of trying I couldn't get a proper solution.

Problem: I want to set constraints (with class NSLayoutAnchor) to get a subview (colorImageView) beeing centered in the superview, with a aspect ratio of 1:1 AND always at the maximum size possible in a superview that sometimes has a width > height OR height > width.

Example 1: SuperViewSize = width : 80, height = 100 -> subview should get a height and width of 80 and centered at (40/50)

Example 2: SuperviewSize = width : 60, height = 50 -> subview should get a height and width of 50 and centered at (30/25)

I dont want /and it does not solve the problem / to hardcode a widthConstraint = min(superviewWidth, superviewHeight) because at the moment of creation the subview, the superviews bounds are not finally defined -> so I need a constraint.

This is my attempt so far (still missing constraints to set height and width at the maximum possible within a varying superview:

// CREATE CONSTRAINTS(center in SuperView)
colorImageView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
colorImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true

// CREATE CONSTRAINTS (ratio 1:1)
colorImageView.widthAnchor.constraint(equalTo: colorImageView.heightAnchor, multiplier: 1.0).isActive = true

I'd really appreciate a hint, how to do that. Thanks in advance :))


Solution

  • The key to making this work is to set the width of the colorImageView equal to the width of its superview but with a priority of 750 (instead of the default 1000).

    Then set the height of the colorImageView to be lessThanOrEqualTo the height of its superview.

    Auto Layout will do its best to satisfy these two new constraints.

    When the view's width is greater than its height, it won't be able to make the colorImageViews width equal to the view's width, but that's OK since the priority is 750. It will make it as big as possible without violating the other full priority constraints.

    When the view's height is greater than its width, it will be able to satisfy all constraints because the colorView's height is lessThanOrEqualTo the view's height.

    import UIKit
    
    let view = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 100))
    view.backgroundColor = .red
    
    let colorImageView = UIView()
    colorImageView.translatesAutoresizingMaskIntoConstraints = false
    colorImageView.backgroundColor = .yellow
    
    view.addSubview(colorImageView)
    
    // CREATE CONSTRAINTS(center in SuperView)
    colorImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    colorImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    
    // CREATE CONSTRAINTS (ratio 1:1)
    colorImageView.widthAnchor.constraint(equalTo: colorImageView.heightAnchor, multiplier: 1.0).isActive = true
    
    let widthConstraint = colorImageView.widthAnchor.constraint(equalTo: view.widthAnchor)
    widthConstraint.priority = UILayoutPriority(rawValue: 750)
    widthConstraint.isActive = true
    
    colorImageView.heightAnchor.constraint(lessThanOrEqualTo: view.heightAnchor).isActive = true
    
    view.layoutIfNeeded()
    
    view.frame = CGRect(x: 0, y: 0, width: 60, height: 50)
    view.layoutIfNeeded()
    

    Here it is running in a Playground:

    enter image description here