shadow of arman
shadow of arman

Reputation: 425

Difference between using NSLayoutConstraints initializer compared to Anchors for setting constraints

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

Answers (2)

landnbloc
landnbloc

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

DonMag
DonMag

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

Related Questions