Reputation: 2490
I'm trying to understand autolayout constraints. Following a Ray W tutorial challenge (without a solution or discussion), the layout should looks like this:
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.
}
}
Upvotes: 6
Views: 12045
Reputation: 36287
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
}
}
Upvotes: 6
Reputation: 154583
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)
Upvotes: 15