Search code examples
swiftautolayoutnslayoutconstraint

Auto Layout programmatically (NSLayoutConstraint): center inner view in outer view and constrain to min(width, height)


I'm not sure what I'm doing wrong using NSLayoutConstraint to generate Auto Layout.

I want to put an inner view centered in an outer view using the smaller of the outer view's width and height. Finally, I want to apply a scaling factor.

Auto Layout Centered

Here's my code.

     NSLayoutConstraint.activate([
            inner.centerXAnchor.constraint(equalTo: outer.centerXAnchor),
            inner.centerYAnchor.constraint(equalTo: outer.centerYAnchor),
            inner.widthAnchor.constraint(equalTo: outer.heightAnchor, multiplier: imageScale),
            inner.widthAnchor.constraint(equalTo: outer.widthAnchor, multiplier: imageScale),
            inner.heightAnchor.constraint(equalTo: inner.widthAnchor)
    ])

This works for:

  • width == height
  • width < height

But when width > height I get this:

enter image description here

What am I missing? This is very easy to do using frames:

let innerWidth = min(outer.frame.width, outer.frame.height) * imageScale
let innerHeight = innerWidth
inner.frame = CGRect(x: (outer.frame.width -innerWidth) / 2, 
                     y: (outer.frame.height - innerHeight) / 2,
                     width: innerWidth, height: innerHeight)

Solution

  • First, it's a bit weird that you are setting the inner.widthAnchor constraint twice in your code. Set it only once.

    Also, you need to select the outer anchor for the inner view's dimensions based on the actual outer view's frame. If the smallest outer dimension is width, then you constrain the width of the inner view to outer.widthAnchor * imageScale. Otherwise, when the smallest outer dimension is height, you constrain to outer.heightAnchor * imageScale.

    This works for me to get the layout you're looking for (I just created a simple single-view project):

    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let inner = UIView()
            inner.backgroundColor = .yellow
            inner.translatesAutoresizingMaskIntoConstraints = false
    
            let outer = UIView()
            outer.backgroundColor = .blue
            outer.translatesAutoresizingMaskIntoConstraints = false
    
            view.addSubview(outer)
            view.addSubview(inner)
    
            // get these from somewhere, e.g. outerWidth.frame.size
            let outerWidth = CGFloat(200)
            let outerHeight = CGFloat(400)
            let outerAnchor: NSLayoutDimension
    
            if outerWidth >= outerHeight {
                outerAnchor = outer.heightAnchor
            } else {
                outerAnchor = outer.widthAnchor
            }
    
            let imageScale = CGFloat(0.5)
    
            NSLayoutConstraint.activate([
                outer.widthAnchor.constraint(equalToConstant: outerWidth),
                outer.heightAnchor.constraint(equalToConstant: outerHeight),
                outer.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                outer.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    
                inner.centerXAnchor.constraint(equalTo: outer.centerXAnchor),
                inner.centerYAnchor.constraint(equalTo: outer.centerYAnchor),
                inner.widthAnchor.constraint(equalTo: outerAnchor, multiplier: imageScale),
                inner.heightAnchor.constraint(equalTo: inner.widthAnchor)
            ])
        }
    }