Eliasz Sawicki

Eliasz Sawicki

iOS developer from Gdansk

PureLayout vs NSLayoutAnchor - Great confrontation

- 15 mins

PureLayout SnapKit Header

Last week I’ve made basic comparison between two libraries that will help you layout your interfaces - PureLayout and SnapKit. You can find this comparison here. Today I’d lake to take the same examples and see how they work with NSLayoutAnchor. NSLayoutAnchor is available to us since iOS 9 and provides us with a new way of creating your constraints. If you do not like creating NSLayoutConstraints using it’s initializers or visual format, and do not want any external dependencies for your layout, then NSLayoutAnchor is for you!

Note before we start:

Simple positioning

Simple positioning

func pureLayout() {
box.autoPinEdge(toSuperviewEdge: .top, withInset: 50)
box.autoPinEdge(toSuperviewEdge: .left, withInset: 20)
box.autoSetDimensions(to: CGSize(width: 100, height: 100))

circle.autoPinEdge(.top, to: .top, of: box)
circle.autoPinEdge(toSuperviewEdge: .right, withInset: 20)
circle.autoSetDimensions(to: CGSize(width: 100, height: 100))

longer.autoPinEdge(toSuperviewEdge: .left, withInset: 20)
longer.autoPinEdge(toSuperviewEdge: .right, withInset: 20)
longer.autoPinEdge(.top, to: .bottom, of: box, withOffset: 40)
longer.autoSetDimension(.height, toSize: 40)
}

func anchorLayout() {
box.translatesAutoresizingMaskIntoConstraints = false
circle.translatesAutoresizingMaskIntoConstraints = false
longer.translatesAutoresizingMaskIntoConstraints = false

box.topAnchor.constraint(equalTo: view.topAnchor, constant: 50).isActive = true
box.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
box.widthAnchor.constraint(equalToConstant: 100).isActive = true
box.heightAnchor.constraint(equalToConstant: 100).isActive = true

circle.topAnchor.constraint(equalTo: box.topAnchor).isActive = true
circle.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
circle.widthAnchor.constraint(equalToConstant: 100).isActive = true
circle.heightAnchor.constraint(equalToConstant: 100).isActive = true

longer.topAnchor.constraint(equalTo: box.bottomAnchor, constant: 40).isActive = true
longer.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
longer.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
longer.heightAnchor.constraint(equalToConstant: 40).isActive = true
}

Inside UIScrollView

ScrollView positioning

func pureLayout() {
scrollView.autoPinEdgesToSuperviewEdges(with: .zero)
scrollView.autoPinEdge(.bottom, to: .bottom, of: longer, withOffset: 20)

box.autoPinEdge(toSuperviewEdge: .top, withInset: 50)
box.autoPinEdge(.left, to: .left, of: self.view, withOffset: 20)
box.autoSetDimensions(to: CGSize(width: 100, height: 100))

circle.autoPinEdge(.top, to: .top, of: box)
circle.autoPinEdge(.right, to: .right, of: self.view, withOffset: -20)
circle.autoSetDimensions(to: CGSize(width: 100, height: 100))

longer.autoPinEdge(.left, to: .left, of: self.view, withOffset: 20)
longer.autoPinEdge(.right, to: .right, of: self.view, withOffset: -20)
longer.autoPinEdge(.top, to: .bottom, of: box, withOffset: 300)
longer.autoSetDimension(.height, toSize: 200)
}

func anchorLayout() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
box.translatesAutoresizingMaskIntoConstraints = false
circle.translatesAutoresizingMaskIntoConstraints = false
longer.translatesAutoresizingMaskIntoConstraints = false

scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: longer.bottomAnchor, constant: 20).isActive = true

box.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 50).isActive = true
box.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
box.widthAnchor.constraint(equalToConstant: 100).isActive = true
box.heightAnchor.constraint(equalToConstant: 100).isActive = true

circle.topAnchor.constraint(equalTo: box.topAnchor).isActive = true
circle.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
circle.widthAnchor.constraint(equalToConstant: 100).isActive = true
circle.heightAnchor.constraint(equalToConstant: 100).isActive = true

longer.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
longer.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
longer.topAnchor.constraint(equalTo: box.bottomAnchor, constant: 300).isActive = true
longer.heightAnchor.constraint(equalToConstant: 200).isActive = true
}

UIScrollView with a surprise

ScrollView positioning

func pureLayout() {
scrollView.autoPinEdgesToSuperviewEdges(with: .zero)
scrollView.autoPinEdge(.bottom, to: .bottom, of: longer, withOffset: 20)

box.autoPinEdge(toSuperviewEdge: .top, withInset: 150)
box.autoAlignAxis(toSuperviewAxis: .vertical)
box.autoSetDimensions(to: CGSize(width: 100, height: 100))

circle.autoPinEdge(.bottom, to: .bottom, of: self.view, withOffset: -20, relation: .lessThanOrEqual)
circle.autoPinEdge(.bottom, to: .top, of: longer, withOffset: -20, relation: .lessThanOrEqual)
circle.autoAlignAxis(toSuperviewAxis: .vertical)
circle.autoSetDimensions(to: CGSize(width: 100, height: 100))

longer.autoPinEdge(.left, to: .left, of: self.view, withOffset: 20)
longer.autoPinEdge(.right, to: .right, of: self.view, withOffset: -20)
longer.autoPinEdge(.top, to: .bottom, of: box, withOffset: 660)
longer.autoSetDimension(.height, toSize: 200)
}

func anchorLayout() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
box.translatesAutoresizingMaskIntoConstraints = false
circle.translatesAutoresizingMaskIntoConstraints = false
longer.translatesAutoresizingMaskIntoConstraints = false

scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: longer.bottomAnchor, constant: 20).isActive = true

box.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 150).isActive = true
box.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
box.widthAnchor.constraint(equalToConstant: 100).isActive = true
box.heightAnchor.constraint(equalToConstant: 100).isActive = true

circle.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor, constant: -20).isActive = true
circle.bottomAnchor.constraint(lessThanOrEqualTo: longer.topAnchor, constant: -20).isActive = true
circle.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
circle.widthAnchor.constraint(equalToConstant: 100).isActive = true
circle.heightAnchor.constraint(equalToConstant: 100).isActive = true

longer.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
longer.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
longer.topAnchor.constraint(equalTo: box.bottomAnchor, constant: 660).isActive = true
longer.heightAnchor.constraint(equalToConstant: 200).isActive = true
}

Updating constraint’s constant

ScrollView positioning

var buttonLeftConstraint: NSLayoutConstraint?

func pureLayout() {
buttonLeftConstraint = animateButton.autoPinEdge(toSuperviewEdge: .left, withInset: 20)
animateButton.autoPinEdge(toSuperviewEdge: .top, withInset: 50)
animateButton.autoSetDimensions(to: CGSize(width: 100, height: 100))
}

func animate() {
UIView.animate(withDuration: 1) {
let random: Double = Double(arc4random_uniform(200))
self.buttonLeftConstraint?.constant = random
self.view.layoutIfNeeded()
}
}

///

var buttonLeftConstraint: NSLayoutConstraint?

func anchorLayout() {
animateButton.translatesAutoresizingMaskIntoConstraints = false
buttonLeftConstraint = animateButton.leftAnchor.constraint(lessThanOrEqualTo: view.leftAnchor, constant: 20)
buttonLeftConstraint?.isActive = true
animateButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 50).isActive = true
animateButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
animateButton.heightAnchor.constraint(equalToConstant: 100).isActive = true
}

func animate() {
UIView.animate(withDuration: 1) {
let random: CGFloat = CGFloat(arc4random_uniform(200))
self.buttonLeftConstraint?.constant = random
self.view.layoutIfNeeded()
}
}

Conclusion

I’m really glad that we have NSLayoutAnchor. If you want to create layout in code and avoid any external layout libraries to do this, then NSLayoutAnchor is a valid choice! It won’t be as neat and comfy as using PureLayout or SnapKit, but for me it’s much much better than visual format or basic NSLayoutConstraint initializer.

What do you think of NSLayoutAnchor? How do you layout your views? Share your thoughts in comments!

This article is cross-posted with my my company blog

comments powered by Disqus
rss facebook twitter github youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora