Thinium
Thinium

Reputation: 303

Inconsistent autolayout behavior between interface builder and programmatically created views

I am trying to programmatically add a subview to a view inside a vertical stack view. It should try to maximize its size to fill its parent view but still keep its aspect ratio. I try to achieve it by adding an aspect ratio constraint with default priority. And add top, bottom, leading, trailing and centerX, centerY constraints with low priority. My prototype in interface builder works perfectly, but when I translate it into codes, the spacing at the borders are missing. Is there something missing in the programmatic version? Here is the code:

class ViewController: UIViewController {
    @IBOutlet var greenView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let testView = UIView()
        testView.backgroundColor = .red
        greenView.addSubview(testView)

        testView.translatesAutoresizingMaskIntoConstraints = false
        let aspectRatioConstraint = NSLayoutConstraint(item: testView,
                            attribute: .width,
                            relatedBy: .equal,
                            toItem: testView,
                            attribute: .height,
                            multiplier: 6.0 / 10.0,
                            constant: 0)

        let leadingConstraint = NSLayoutConstraint(item: testView,
                                             attribute: .leading,
                                             relatedBy: .equal,
                                             toItem: greenView,
                                             attribute: .leading,
                                             multiplier: 1,
                                             constant: 5)

        let trailingConstraint = NSLayoutConstraint(item: testView,
                                             attribute: .trailing,
                                             relatedBy: .equal,
                                             toItem: greenView,
                                             attribute: .trailing,
                                             multiplier: 1,
                                             constant: 5)

        let topConstraint = NSLayoutConstraint(item: testView,
                                             attribute: .top,
                                             relatedBy: .equal,
                                             toItem: greenView,
                                             attribute: .top,
                                             multiplier: 1,
                                             constant:5)

        let bottomConstraint = NSLayoutConstraint(item: testView,
                                             attribute: .bottom,
                                             relatedBy: .equal,
                                             toItem: greenView,
                                             attribute: .bottom,
                                             multiplier: 1,
                                             constant: 5)

        let centerXConstraint = NSLayoutConstraint(item: testView,
                                             attribute: .centerX,
                                             relatedBy: .equal,
                                             toItem: greenView,
                                             attribute: .centerX,
                                             multiplier: 1,
                                             constant: 0)

        let centerYConstraint = NSLayoutConstraint(item: testView,
                                             attribute: .centerY,
                                             relatedBy: .equal,
                                             toItem: greenView,
                                             attribute: .centerY,
                                             multiplier: 1,
                                             constant: 0)

        leadingConstraint.priority = .defaultLow
        trailingConstraint.priority = .defaultLow
        topConstraint.priority = .defaultLow
        bottomConstraint.priority = .defaultLow
        centerXConstraint.priority = .defaultLow
        centerYConstraint.priority = .defaultLow

        NSLayoutConstraint.activate([aspectRatioConstraint, leadingConstraint, trailingConstraint, topConstraint, bottomConstraint, centerXConstraint, centerYConstraint])

    }
}

The broken version created programmatically: The top and bottom spacing are missing.

enter image description here

The expected look built with Interface Builder:

built with interface builder

Here is the settings in IB:

here is the setting in IB

Upvotes: 0

Views: 49

Answers (1)

DonMag
DonMag

Reputation: 77423

I think what you want is to use greaterThanOrEqual for top and leading, and lessThanOrEqual for right and bottom.

Try it like this:

class ThiniumViewController: UIViewController {
    @IBOutlet var greenView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let testView = UIView()
        testView.backgroundColor = .red

        greenView.addSubview(testView)

        testView.translatesAutoresizingMaskIntoConstraints = false

        let tvHeightConstraint = testView.heightAnchor.constraint(equalTo: testView.widthAnchor, multiplier: 10.0 / 6.0)
        tvHeightConstraint.priority = .defaultHigh

        let tvWidthConstraint = testView.widthAnchor.constraint(equalTo: greenView.widthAnchor, multiplier: 1.0)
        tvWidthConstraint.priority = .defaultHigh

        NSLayoutConstraint.activate([

            // center testView in greenView
            testView.centerXAnchor.constraint(equalTo: greenView.centerXAnchor),
            testView.centerYAnchor.constraint(equalTo: greenView.centerYAnchor),

            // constrain top and leading of redView to *at least* 5-points
            testView.topAnchor.constraint(greaterThanOrEqualTo: greenView.topAnchor, constant: 5.0),
            testView.leadingAnchor.constraint(greaterThanOrEqualTo: greenView.leadingAnchor, constant: 5.0),

            // constrain height of testView to width of testView at 10:6 ratio at Priority 750
            tvHeightConstraint,

            // constrain width of testView to width of greenView at Priority 750
            tvWidthConstraint,

            // constrain bottom and trailing of redView to *at least* 5-points
            testView.bottomAnchor.constraint(lessThanOrEqualTo: greenView.bottomAnchor, constant: -5.0),
            testView.trailingAnchor.constraint(lessThanOrEqualTo: greenView.trailingAnchor, constant: -5.0),

        ])

    }
}

As a side note, you may find it easier to write - and more readable - to construct your constraints as shown in my sample code.

Result:

enter image description here

Upvotes: 1

Related Questions