以编程方式添加 CenterX/CenterY 约束

我有一个 UITableViewController,如果没有什么可显示的,它不显示任何部分。我已经添加了一个标签来向用户表明这段代码没有什么可以显示的:

label = UILabel(frame: CGRectMake(20, 20, 250, 100))
label.text = "Nothing to show"
self.tableView.addSubview(label)

但是现在,我想让它水平和垂直地居中。通常,我会选择屏幕截图中高亮显示的两个选项(加上高度和宽度选项) :

enter image description here

我尝试了下面的代码来添加这些约束,但是这个应用程序出现了错误:

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)

错误:

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

标签应该总是水平和垂直居中,因为应用程序支持设备的旋转。

我做错了什么? 我如何成功地添加这些约束?

谢谢!

129866 次浏览

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)

Programmatically you can do it by adding the following constraints.

NSLayoutConstraint *constraintHorizontal = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.superview
attribute:attribute
multiplier:1.0f
constant:0.0f];


NSLayoutConstraint *constraintVertical = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.superview
attribute:attribute
multiplier:1.0f
constant:0.0f];

Center in container

enter image description here

The code below does the same thing as centering in the Interface Builder.

override func viewDidLoad() {
super.viewDidLoad()


// set up the view
let myView = UIView()
myView.backgroundColor = UIColor.blue
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)


// Add code for one of the constraint methods below
// ...
}

Method 1: Anchor Style

myView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
myView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

Method 2: NSLayoutConstraint Style

NSLayoutConstraint(item: myView, attribute: NSLayoutConstraint.Attribute.centerX, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.centerX, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: myView, attribute: NSLayoutConstraint.Attribute.centerY, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.centerY, multiplier: 1, constant: 0).isActive = true

Notes

  • Anchor style is the preferred method over NSLayoutConstraint Style, however it is only available from iOS 9, so if you are supporting iOS 8 then you should still use NSLayoutConstraint Style.
  • You will also need to add length and width constraints.
  • My full answer is here.

If you don't care about this question being specifically about a tableview, and you'd just like to center one view on top of another view here's to do it:

    let horizontalConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: parentView, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0)
parentView.addConstraint(horizontalConstraint)


let verticalConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: parentView, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0)
parentView.addConstraint(verticalConstraint)

The ObjectiveC equivalent is:

    myView.translatesAutoresizingMaskIntoConstraints = NO;


[[myView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor] setActive:YES];


[[myView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor] setActive:YES];

In Swift 5 it looks like this:

label.translatesAutoresizingMaskIntoConstraints = false
label.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor).isActive = true

A solution for me was to create a UILabel and add it to the UIButton as a subview. Finally I added a constraint to center it within the button.

UILabel * myTextLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 75, 75)];
myTextLabel.text = @"Some Text";
myTextLabel.translatesAutoresizingMaskIntoConstraints = false;


[myButton addSubView:myTextLabel];


// Add Constraints
[[myTextLabel centerYAnchor] constraintEqualToAnchor:myButton.centerYAnchor].active = true;
[[myTextLabel centerXAnchor] constraintEqualToAnchor:myButton.centerXAnchor].active = true;