Programmatically Change Multiplier of Alignment Constraint of Center X/Y

Programmatically Change multiplier of Alignment Constraint of Center X / Y

So for Y, if you set the top of the image equal with a constant of 0 to the top of the superView. then enter this code:

@IBOutlet weak var topc: NSLayoutConstraint!

let heightOfSuperview = self.view.bounds.height
topc.constant = heightOfSuperview * 0.90 // this has the same effect as multiplier

This is equivalent of Center.y multiplier = 0.9:1

How to programmatically add constraint center with multiplier

So it's possible, I missused the centerXAnchor instead of using .centerX

Also the order in which I called each item was not correct:

// Not Working
NSLayoutConstraint(item: self, attribute: centerXAnchor, relatedBy: .equal, toItem: buttonLeft, attribute: centerXAnchor, multiplier: 0.5, constant: 0)

// Working
NSLayoutConstraint(item: buttonLeft, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 0.5, constant: 0)

Though I could not find any way to create the constraint using the anchors methods.

Programmatically Add CenterX/CenterY Constraints

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)

Can I change multiplier property for NSLayoutConstraint?

If you have only have two sets of multipliers that need to be applied, from iOS8 onwards you can add both sets of constraints and decide which should be active at any time:

NSLayoutConstraint *standardConstraint, *zoomedConstraint;

// ...
// switch between constraints
standardConstraint.active = NO; // this line should always be the first line. because you have to deactivate one before activating the other one. or they will conflict.
zoomedConstraint.active = YES;
[self.view layoutIfNeeded]; // or using [UIView animate ...]

Swift 5.0 version

var standardConstraint: NSLayoutConstraint!
var zoomedConstraint: NSLayoutConstraint!

// ...

// switch between constraints
standardConstraint.isActive = false // this line should always be the first line. because you have to deactivate one before activating the other one. or they will conflict.
zoomedConstraint.isActive = true
self.view.layoutIfNeeded() // or using UIView.animate

Programmatic Constraints not working as expected

The main problems I see with your code:

  1. You are adding and removing constraints every time updateConstraints is called. You only need to set up the constraints once when you create your view.

  2. You are setting translatesAutoresizingMaskIntoConstraints = false on ABSegment itself. Don't do this. This tells Auto Layout that you will be specifying the size and location of ABSegment using constraints, and you clearly are using a frame for this purpose.


Here is the refactored code:

class ABSegment: UIView {

let titleLabel = UILabel()

// Computed property to allow title to be changed
var title: String {
set {
titleLabel.text = newValue
}
get {
return titleLabel.text ?? ""
}
}

override init(frame: CGRect) {
super.init(frame: frame)

setupLabel(title: "")
}

convenience init(title: String) {

self.init(frame: CGRect.zero)

self.title = title
}

// This init is called if your view is
// set up in the Storyboard
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)

setupLabel(title: "")
}

func setupLabel(title: String) {

titleLabel.text = title

titleLabel.textAlignment = .center

addSubview(titleLabel)

backgroundColor = UIColor.blue

titleLabel.translatesAutoresizingMaskIntoConstraints = false

let centerX = NSLayoutConstraint(item: titleLabel, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)

let centerY = NSLayoutConstraint(item: titleLabel, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)

let width = NSLayoutConstraint(item: titleLabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)

let height = NSLayoutConstraint(item: titleLabel, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: 0)

NSLayoutConstraint.activate([centerX, centerY, width, height])
}

override func layoutSubviews() {
super.layoutSubviews()
logFrames()
}

func logFrames() {
print("self.frame is \(frame)")
print("titleLabel.frame is \(titleLabel.frame)")
}

override func updateConstraints() {
super.updateConstraints()
logFrames()
}

}

Notes:

  1. I moved all of the setup of the label into a function called setupLabel. This allows it to be called by both initializers.
  2. I added a computed property called title to ABSegment which allows you to change the title at any time with mysegment.title = "new title".
  3. I turned init(withSegment:) into a convenience init. It calls the standard init(frame:) and then sets the title. It is not a common practice to use withProperty so I changed it. You'd create an ABSegment with var segment = ABSegment(title: "some title").
  4. I had required init?(coder aDecoder: NSCoder) call setupLabel so that ABSegment can be used with views added in the Storyboard.
  5. The overrides of layoutSubviews and updateConstraints were left in to log the frames. That is all that they do now.


Related Topics



Leave a reply



Submit