DrWhat DrWhat - 1 year ago 152
Swift Question

Constraints in code using swift - unable to activate

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.
}


}

Answer Source

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)
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download