Constraints in code using swift - unable to activate

11,770

Solution 1

My first question is: if the labels have an intrinsic content size, and everything else is pinned to the yellow view, then why do I need to define a fixed width and height? Why wouldn't it infer the width and height from the intrinsic content of its subviews?

The username and password fields don't have a width. They get their widths by being pinned to the labels on the left and the right edge of the centerView. If you added constraints to give these fields a width, then the centerView could determine its width from its contents.


Moving on and trying to recreate this layout in code was giving me an error: Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with items > and > because they have no common ancestor. Does the constraint reference items in different view hierarchies? That's illegal.'

Your first problem is that you didn't add the passwordLabel as a subview:

self.centerView.addSubview(passwordLabel)

That is what was giving you the error. The passwordLabel was not in the view hierarchy, so Auto Layout didn't know how to constrain it with the centerView.

Your second problem is that you set the width and height of the centerView to 0. Try larger values like 100 and 300:

let constraintCenterViewHeight = NSLayoutConstraint(item: centerView, attribute: .Height,
    relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0,
    constant: 100)

let constraintCenterViewWidth = NSLayoutConstraint(item: centerView, attribute: .Width,
    relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0,
    constant: 300)

Your third problem is that some of your constants need to be negative:

// changed to -8
let constraintUsernameLabelHTrailing = NSLayoutConstraint(item: usernameLabel,
    attribute: .Trailing, relatedBy: .Equal, toItem: usernameField,
    attribute: .Leading, multiplier: 1.0, constant: -8)

// changed to -8
let constraintUsernameFieldVBottom = NSLayoutConstraint(item: usernameField,
    attribute: .Bottom, relatedBy: .Equal, toItem: passwordField,
    attribute: .Top, multiplier: 1.0, constant: -8)

// changed to -8
let constraintPasswordLabelHTrailing = NSLayoutConstraint(item: passwordLabel,
    attribute: .Trailing, relatedBy: .Equal, toItem: passwordField,
    attribute: .Leading, multiplier: 1.0, constant: -8)

Solution 2

I got the same error, but the reason it happened was something different:

I got the error because I referenced to my superview before I added the view to its superview. <-- writing your code as such is no different than not including view.addSubview(stackView) itself. Both would generate the same error—because the view has no defined relation to its superview.

In my code below, lines B1, B2 are happening before I add the view to the superview (in line AA).

This is my code:

class ViewController: UIViewController {

    var label = UILabel()
    var button = UIButton()
    var stackView = UIStackView()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.cyan
        navigationItem.title = "VCTitle"
        setupUI()
    }

    private func setupUI(){

        label.backgroundColor = UIColor.blue
        label.heightAnchor.constraint(equalToConstant: 50).isActive = true
        label.widthAnchor.constraint(equalToConstant: 80).isActive = true

        button.backgroundColor = UIColor.purple
        button.heightAnchor.constraint(equalToConstant: 30).isActive = true
        button.widthAnchor.constraint(equalToConstant: 10).isActive = true

        setupStackView()

        stackView.addArrangedSubview(label)
        stackView.addArrangedSubview(button)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView) // AA


    }

    private func setupStackView(){

        stackView.axis = UILayoutConstraintAxis.vertical
        stackView.distribution = UIStackViewDistribution.equalSpacing
        stackView.alignment = UIStackViewAlignment.center
        stackView.spacing = 15

        stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true // B1
        stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true // B2

    }


}

The correct way to do it is to move line B1, B2 to anywhere after line AA.

class ViewController: UIViewController {

    var label = UILabel()
    var button = UIButton()
    var stackView = UIStackView()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.cyan
        navigationItem.title = "VCTitle"
        setupUI()
    }

    private func setupUI(){

        label.backgroundColor = UIColor.blue
        label.heightAnchor.constraint(equalToConstant: 50).isActive = true
        label.widthAnchor.constraint(equalToConstant: 80).isActive = true

        button.backgroundColor = UIColor.purple
        button.heightAnchor.constraint(equalToConstant: 30).isActive = true
        button.widthAnchor.constraint(equalToConstant: 10).isActive = true

        setupStackView()

        stackView.addArrangedSubview(label)
        stackView.addArrangedSubview(button)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView) //AA

        // The 2 lines below are now in the right place.
        stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true // B1
        stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true // B2


    }

    private func setupStackView(){

        stackView.axis = UILayoutConstraintAxis.vertical
        stackView.distribution = UIStackViewDistribution.equalSpacing
        stackView.alignment = UIStackViewAlignment.center
        stackView.spacing = 15

    }
}
Share:
11,770
DrWhat
Author by

DrWhat

Looking to join a team of Swift iOS developers.

Updated on June 05, 2022

Comments

  • DrWhat
    DrWhat almost 2 years

    I'm trying to understand autolayout constraints. Following a Ray W tutorial challenge (without a solution or discussion), the layout should looks like this:

    desired layout

    Doing it in IB was easy enough - create a yellow view with a width and height, vertically centered and horizontally pinned to the margins; then create labels, fields and a button with simple constraints inside that view.

    My first question is: if the labels have an intrinsic content size, and everything else is pinned to the yellow view, then why do I need to define a fixed width and height? Why wouldn't it infer the width and height from the intrinsic content of its subviews?

    Moving on and trying to recreate this layout in code was giving me an error:

    Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with items <UILabel: 0x7a961d60; frame = (0 0; 0 0); text = 'Password:'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7a961e40>> and <UIView: 0x7a96b6f0; frame = (0 0; 0 0); layer = <CALayer: 0x7a96b880>> because they have no common ancestor. Does the constraint reference items in different view hierarchies? That's illegal.'

    Second question: what are these layers, and all my views are in a heirachy - superview contains the yellow view contains the text labels and fields.

    After much pain, I tried to exactly recreate the constraints made in IB, but this only added the following error: Unable to simultaneously satisfy constraints.

    (
        "<NSLayoutConstraint:0x7ac57370 H:[UIView:0x7a96b6f0(0)]>",
        "<NSLayoutConstraint:0x7ac57400 H:|-(8)-[UILabel:0x7a96bb50'Username:']   (Names: '|':UIView:0x7a96b6f0 )>",
        "<NSLayoutConstraint:0x7ac57430 UILabel:0x7a96bb50'Username:'.trailing == UITextField:0x7a961020.leading + 8>",
        "<NSLayoutConstraint:0x7ac57520 UITextField:0x7a961020.trailing == UIView:0x7a96b6f0.trailing - 8>" )
    

    Final question(s): How do I know what view is view 0x7aetc? And where is this constraint in my code? The rest look ok(?).

    I must be doing something very wrong at some basic level.

    Here is my code:

    import UIKit
    
    class ViewController: UIViewController {
    
        let centerView = UIView()
        let usernameLabel = UILabel()
        let passwordLabel = UILabel()
        let usernameField = UITextField()
        let passwordField = UITextField()
        let submitButton = UIButton()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            centerView.backgroundColor = UIColor.yellowColor()
            usernameLabel.text = "Username:"
            passwordLabel.text = "Password:"
            usernameField.borderStyle = .RoundedRect
            passwordField.borderStyle = .RoundedRect
            submitButton.setTitle("Submit!", forState: .Normal)
            submitButton.setTitleColor(UIColor.blackColor(), forState: .Normal)
            submitButton.setTitleColor(UIColor.blueColor(), forState: .Highlighted)
    
            view.addSubview(centerView)
            self.centerView.addSubview(usernameField)
            self.centerView.addSubview(passwordField)
            self.centerView.addSubview(usernameLabel)
            self.centerView.addSubview(submitButton)
    
            let constraintCenterViewHeight = NSLayoutConstraint(item: centerView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 0)
            let constraintCenterViewWidth = NSLayoutConstraint(item: centerView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 0)
    
            let constraintCenterViewCenterX = NSLayoutConstraint(item: centerView, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1.0, constant: 0)
            let constraintCenterViewCenterY = NSLayoutConstraint(item: centerView, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1.0, constant: 0)
    
            let constraintUsernameLabelHLeading = NSLayoutConstraint(item: usernameLabel, attribute: .Leading, relatedBy: .Equal, toItem: centerView, attribute: .Leading, multiplier: 1.0, constant: 8)
            let constraintUsernameLabelHTrailing = NSLayoutConstraint(item: usernameLabel, attribute: .Trailing, relatedBy: .Equal, toItem: usernameField, attribute: .Leading, multiplier: 1.0, constant: 8)
            let constraintUsernameLabelAlignBottom = NSLayoutConstraint(item: usernameLabel, attribute: .Bottom, relatedBy: .Equal, toItem: usernameField, attribute: .Bottom, multiplier: 1.0, constant: 0)
    
            let constraintUsernameFieldVTop = NSLayoutConstraint(item: usernameField, attribute: .Top, relatedBy: .Equal, toItem: centerView, attribute: .Top, multiplier: 1.0, constant: 8)
            let constraintUsernameFieldHTrailing = NSLayoutConstraint(item: usernameField, attribute: .Trailing, relatedBy: .Equal, toItem: centerView, attribute: .Trailing, multiplier: 1.0, constant: -8)
            let constraintUsernameFieldVBottom = NSLayoutConstraint(item: usernameField, attribute: .Bottom, relatedBy: .Equal, toItem: passwordField, attribute: .Top, multiplier: 1.0, constant: 8)
    
            let constraintPasswordLabelHLeading = NSLayoutConstraint(item: passwordLabel, attribute: .Leading, relatedBy: .Equal, toItem: centerView, attribute: .Leading, multiplier: 1.0, constant: 8)
            let constraintPasswordLabelHTrailing = NSLayoutConstraint(item: passwordLabel, attribute: .Trailing, relatedBy: .Equal, toItem: passwordField, attribute: .Leading, multiplier: 1.0, constant: 8)
            let constraintPasswordLabelAlignBottom = NSLayoutConstraint(item: passwordLabel, attribute: .Bottom, relatedBy: .Equal, toItem: passwordField, attribute: .Bottom, multiplier: 1.0, constant: 0)
    
            let constraintPasswordFieldHTrailing = NSLayoutConstraint(item: passwordField, attribute: .Trailing, relatedBy: .Equal, toItem: centerView, attribute: .Trailing, multiplier: 1.0, constant: -8)
    
            centerView.setTranslatesAutoresizingMaskIntoConstraints(false)
            usernameLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
            usernameField.setTranslatesAutoresizingMaskIntoConstraints(false)
            passwordField.setTranslatesAutoresizingMaskIntoConstraints(false)
            passwordLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
           // submitButton.setTranslatesAutoresizingMaskIntoConstraints(false)
    
            NSLayoutConstraint.activateConstraints([constraintCenterViewHeight, constraintCenterViewWidth, constraintCenterViewCenterX, constraintCenterViewCenterY, constraintUsernameLabelHLeading,
                constraintUsernameLabelHTrailing, constraintUsernameLabelAlignBottom, constraintUsernameFieldVTop, constraintUsernameFieldHTrailing, constraintUsernameFieldVBottom, constraintPasswordLabelHLeading, constraintPasswordLabelHTrailing, constraintPasswordLabelAlignBottom, constraintPasswordFieldHTrailing])
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
    
    }
    
  • DrWhat
    DrWhat over 8 years
    I looked at this code for days and never saw the missing subview, nor the width and height as 0! Ah-ha, (as I was writing that I didn't get it) my dull old brain thought the usernameLabel's trailing edge to the usernameField's leading edge should be 8 points further along the Y or horizontal axis. BUT that's just another stupid mistake. I want the usernameLabel's trailing edge to the usernameField's leading edge 8 points back along the Y or horizontal axis. Very sorry, and very thankful that you pointed this out. It is great to know that this stuff works if you understand it.
  • Alessandro Ornano
    Alessandro Ornano almost 6 years
    The 2 lines are the goal (centerXAnchor and centerYAnchor) It should be reported to all the stackView layout guides. You are the hero of the day, thank you.