I have a UITableViewController that doesn't display any sections if there is nothing to show. I've added a label to indicate to the user that there is nothing to display with this code:
label = UILabel(frame: CGRectMake(20, 20, 250, 100))
label.text = "Nothing to show"
self.tableView.addSubview(label)
But now, I want it to be centered Horizontally and Vertically. Normally, I would choose the two options highlighted (plus ones for height and width) in the screenshot:
I've tried the following code to add the constraints but the app crashes with error:
label = UILabel(frame: CGRectMake(20, 20, 250, 100))
label.text = "Nothing to show"
let xConstraint = NSLayoutConstraint(item: label, attribute: .CenterX, relatedBy: .Equal, toItem: self.tableView, attribute: .CenterX, multiplier: 1, constant: 0)
let yConstraint = NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: self.tableView, attribute: .CenterY, multiplier: 1, constant: 0)
label.addConstraint(xConstraint)
label.addConstraint(yConstraint)
error:
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView _viewHierarchyUnpreparedForConstraint:] to debug.
2014-12-23 08:17:36.755 [982:227877] *** Assertion failure in -[UILabel _layoutEngine_didAddLayoutConstraint:roundingAdjustment:mutuallyExclusiveConstraints:], /SourceCache/UIKit/UIKit-3318.16.21/NSLayoutConstraint_UIKitAdditions.m:560
The label should always center horizontally and vertically because the app supports rotation of the device.
What am I doing wrong? How do I add these constraints successfully?
Thanks!
Update for Swift 3/Swift 4:
As of iOS 8, you can and should activate your constraints by setting their isActive
property to true
. This enables the constraints to add themselves to the proper views. You can activate multiple constraints at once by passing an array containing the constraints to NSLayoutConstraint.activate()
let label = UILabel(frame: CGRect.zero)
label.text = "Nothing to show"
label.textAlignment = .center
label.backgroundColor = .red // Set background color to see if label is centered
label.translatesAutoresizingMaskIntoConstraints = false
self.tableView.addSubview(label)
let widthConstraint = NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal,
toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 250)
let heightConstraint = NSLayoutConstraint(item: label, attribute: .height, relatedBy: .equal,
toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100)
let xConstraint = NSLayoutConstraint(item: label, attribute: .centerX, relatedBy: .equal, toItem: self.tableView, attribute: .centerX, multiplier: 1, constant: 0)
let yConstraint = NSLayoutConstraint(item: label, attribute: .centerY, relatedBy: .equal, toItem: self.tableView, attribute: .centerY, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([widthConstraint, heightConstraint, xConstraint, yConstraint])
Better Solution:
Since this question was originally answered, layout anchors were introduced making it much easier to create the constraints. In this example I create the constraints and immediately activate them:
label.widthAnchor.constraint(equalToConstant: 250).isActive = true
label.heightAnchor.constraint(equalToConstant: 100).isActive = true
label.centerXAnchor.constraint(equalTo: self.tableView.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: self.tableView.centerYAnchor).isActive = true
or the same using NSLayoutConstraint.activate()
:
NSLayoutConstraint.activate([
label.widthAnchor.constraint(equalToConstant: 250),
label.heightAnchor.constraint(equalToConstant: 100),
label.centerXAnchor.constraint(equalTo: self.tableView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: self.tableView.centerYAnchor)
])
Note: Always add your subviews to the view hierarchy before creating and activating the constraints.
Original Answer:
The constraints make reference to self.tableView
. Since you are adding the label as a subview of self.tableView
, the constraints need to be added to the "common ancestor":
self.tableView.addConstraint(xConstraint)
self.tableView.addConstraint(yConstraint)
As @mustafa and @kcstricks pointed out in the comments, you need to set label.translatesAutoresizingMaskIntoConstraints
to false
. When you do this, you also need to specify the width
and height
of the label with constraints because the frame no longer is used. Finally, you also should set the textAlignment
to .Center
so that your text is centered in your label.
var label = UILabel(frame: CGRectZero)
label.text = "Nothing to show"
label.textAlignment = .Center
label.backgroundColor = UIColor.redColor() // Set background color to see if label is centered
label.translatesAutoresizingMaskIntoConstraints = false
self.tableView.addSubview(label)
let widthConstraint = NSLayoutConstraint(item: label, attribute: .Width, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 250)
label.addConstraint(widthConstraint)
let heightConstraint = NSLayoutConstraint(item: label, attribute: .Height, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 100)
label.addConstraint(heightConstraint)
let xConstraint = NSLayoutConstraint(item: label, attribute: .CenterX, relatedBy: .Equal, toItem: self.tableView, attribute: .CenterX, multiplier: 1, constant: 0)
let yConstraint = NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: self.tableView, attribute: .CenterY, multiplier: 1, constant: 0)
self.tableView.addConstraint(xConstraint)
self.tableView.addConstraint(yConstraint)