Reputation: 425
Why do some developers add constraints like this:
NSLayoutConstraint(item: myView, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1.0, constant: 20.0).isActive = true
And some like this:
myView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 20).isActive = true
They basically do the same thing... right? So what is the difference between them? Why should one be used instead of the other? Is there a performance difference for using one over the other?
At the place I worked our iOS lead was using the NSLayoutConstraint
initialization way exclusively and everyone was forced to do the same to have more consistency and readability throughout the entire code, I like both ways, I just want to know if there was/is ever any benefit of using one over the other? Or are the differences just based on preference?
Upvotes: 1
Views: 1114
Reputation: 1068
Anchors were added later. They look cleaner and you can more easily constrain to the safe area as it has its own anchors.
Upvotes: 0
Reputation: 77511
In large part, it is new syntax and readability, but there are still some things that you can do with NSLayoutConstraint(...)
that you cannot do the "new" way.
For example, let's take a simple task of adding a UILabel
centered horizontally, 40-pts from the bottom. Each of the following examples will begin with this:
override func viewDidLoad() {
super.viewDidLoad()
let myView = UILabel()
myView.backgroundColor = .green
myView.text = "Hello"
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
// add constraints....
}
So, our first method looks like this:
// center horizontally
NSLayoutConstraint(item: myView,
attribute: .centerX,
relatedBy: .equal,
toItem: view,
attribute: .centerX,
multiplier: 1.0,
constant: 0.0).isActive = true
// bottom = 40-pts from view Bottom
NSLayoutConstraint(item: myView,
attribute: .bottom,
relatedBy: .equal,
toItem: view,
attribute: .bottom,
multiplier: 1.0,
constant: -40.0).isActive = true
We can get the exact same results using this syntax, which would generally be considered more "readable":
// center horizontally
myView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
// bottom = 40-pts from view Bottom
myView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40.0).isActive = true
Now, we'll usually have many more constraints to set, so we can make it even more readable with this (eliminates the .isActive = true
at the end of every line):
NSLayoutConstraint.activate([
// center horizontally
myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// bottom = 40-pts from view Bottom
myView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40.0),
])
So... What happens if we throw in just a little complexity, such as saying "Keep the Bottom of the Label 20% from the bottom of the view"?
To stick with the "new, more readable" syntax, we have a couple options...
1 - constrain the bottom of the Label to the bottom of the view, wait until layout is finished - so we know the height of the view - and then set the .constant
on the bottom anchor to -(view height * 0.2)
. That will work, but we have to re-calculate every time the label's superview changes (such as on device rotation).
2 - add a UIView
as a "bottom spacer":
// add a hidden UIView for bottom "space"
let spacerView = UIView()
spacerView.isHidden = true
view.addSubview(spacerView)
spacerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// spacerView at bottom, height = 20% of view height
spacerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
spacerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.2),
// center horizontally
myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// bottom = spacerView Top
myView.bottomAnchor.constraint(equalTo: spacerView.topAnchor, constant: 0.0),
])
That works, and handles superview size changes, but we've added another view to the view hierarchy. For this simple example, no big deal, but we probably don't want to add a bunch of them for a complex layout.
3 - add a UILayoutGuide
as a "bottom spacer":
// add a UILayoutGuide for bottom "space"
let spacerGuide = UILayoutGuide()
view.addLayoutGuide(spacerGuide)
NSLayoutConstraint.activate([
// spacerGuide at bottom, height = 20% of view height
spacerGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
spacerGuide.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.2),
// center horizontally
myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// bottom = spacerGuide Top
myView.bottomAnchor.constraint(equalTo: spacerGuide.topAnchor, constant: 0.0),
])
Accomplishes the same thing, but now we're using a non-rendering UI element so we're not weighing down the view hierarchy.
4 - Use the NSLayoutConstraint(...)
syntax, and avoid all of that:
// center horizontally
NSLayoutConstraint(item: myView,
attribute: .centerX,
relatedBy: .equal,
toItem: view,
attribute: .centerX,
multiplier: 1.0,
constant: 0.0).isActive = true
// bottom = 80% of view bottom (leaves 20% space at bottom)
NSLayoutConstraint(item: myView,
attribute: .bottom,
relatedBy: .equal,
toItem: view,
attribute: .bottom,
multiplier: 0.8,
constant: 0.0).isActive = true
}
So, for most situations, it is a matter of preference and/or consistency, but you will find the occasional case where there is a difference.
Upvotes: 6