From Springs & Struts to Autolayout and Anchors

In the beginning there was only 480x320. No constraints, no adaptivity, just frames and bounds. In a world like this, you could do anything. Creating a new view with a fix size? Easy thing, init(frame:) was always there for you. But times have changed, we've got many screens with different sizes. If you are still calculating frames and positions, this tutorial will help you to move forward to the latest auto layout technology called anchors.

Let me introduce you to Mr Square. He's a very precise object, he is always at the position 32,32 with a fix size 64,64. I am going to show you many ways how to define Mr Square's attributes:

Springs & Struts

//somewhere inside a viewDidLoad method...
let square = UIView(frame: CGRect(x: 32, y: 32, width: 64, height: 64))
square.backgroundColor = self.squareColor
self.view.addSubview(square)
![](/content/images/2016/05/Simulator-Screen-Shot-2016--m-j--12--20-47-43.png)
The result is exactly what you expect. Now imagine that I want to move Mr Square to the center of the screen? How should I do that in a springs & struts only world? Of course, let's grab the screen size, and do some math.

let centerX = CGRectGetMidX(UIScreen.mainScreen().bounds)
let centerY = CGRectGetMidY(UIScreen.mainScreen().bounds)
let squareSize = CGFloat(64)
let square = UIView(frame: CGRect(x: centerX-squareSize/2, y: centerY-squareSize/2, width: squareSize, height: squareSize))
square.backgroundColor = self.squareColor
self.view.addSubview(square)
![](/content/images/2016/05/Simulator-Screen-Shot-2016--m-j--12--20-46-33.png)

Seems perfect, right?

![](/content/images/2016/05/Simulator-Screen-Shot-2016--m-j--12--20-46-47.png)

Not at all.

I will show you how to fix this and tell you why springs and struts sucks.

class ViewController: UIViewController
{

	//this will be a retain cycle don't judge me. :)
	var square: UIView?
	
	let squareColor = UIColor(hex: 0xf42574)
	var squareFrame: CGRect {
        let centerX    = CGRectGetMidX(UIScreen.mainScreen().bounds)
        let centerY    = CGRectGetMidY(UIScreen.mainScreen().bounds)
        let squareSize = CGFloat(64)

		return CGRect(x: centerX-squareSize/2, y: centerY-squareSize/2, width: squareSize, height: squareSize)
	}
	
	
	override func viewDidLayoutSubviews() {
		super.viewDidLayoutSubviews()
	
		self.square?.frame = self.squareFrame
	}
	
	override func viewDidLoad() {
		super.viewDidLoad()
		
		self.square = UIView(frame: self.squareFrame)
		self.square?.backgroundColor = self.squareColor
		self.view.addSubview(self.square!)
        }		
}

Voilá, Mr Square is on the right spot.

![](/content/images/2016/05/Simulator-Screen-Shot-2016--m-j--12--20-46-36.png)

So, I had to create a property for Mr Square, and I have to recalculate - manually - his frame every time the device rotates. Is this working? Sure! Is it good? No! This is just a simple square and I have to calculate a lot. Imagine if you have 10 or more objects... no way I am not going to do this. Let's get out of here. Good bye Mr Square.

Auto Layout

With iOS6 Apple brought us the holy grail of layout technologies. It was the perfect successor of the previous system. Everyone adapted it fast, that's why Apple engineers completely removed frame based layout (S&S) in the next release... sorry, just kidding. :) People hated it by the first sight. But it was the beginning of a new era.

I know it's a long trip until we can completely kill frames from UIKit, but we are getting there slowly. The init hell is mostly because of Springs & Struts. That's another reason why I am completely against it.

Dear Apple, remove init(frame) methods from UIKit already. And if you are there then please delete that required flag before init(coder). Not everyone wants to use his own class from IB.

Anyway, get back to work. And let me introduce you to Mr Circle. He is a very 'constrained' person. His size is always 64 and he is always in the middle of nowhere. :)

let circle = UIView(frame: CGRectZero)
		circle.translatesAutoresizingMaskIntoConstraints = false
		circle.layer.cornerRadius = 32
		circle.backgroundColor = self.circleColor
		self.view.addSubview(circle)
		
		self.view.addConstraints([
			NSLayoutConstraint(item: circle, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .Width, multiplier: 1.0, constant: 64),
			NSLayoutConstraint(item: circle, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 1.0, constant: 64),
			NSLayoutConstraint(item: circle, attribute: .CenterX, relatedBy: .Equal, toItem: self.view, attribute: .CenterX, multiplier: 1.0, constant: 0),
			NSLayoutConstraint(item: circle, attribute: .CenterY, relatedBy: .Equal, toItem: self.view, attribute: .CenterY, multiplier: 1.0, constant: 0),
		])
![](/content/images/2016/05/Simulator-Screen-Shot-2016--m-j--12--21-12-14.png)
![](/content/images/2016/05/Simulator-Screen-Shot-2016--m-j--12--21-12-17.png)

So as you can see from the example above, with constraints we can specify some attributes for our view objects, and the layout system will try to enforce all the views to keep these constraints. But what is a constraint? Actually the anatomy of a constraint is pretty simple. If you want to understand Auto layout, you should definitely read about the system on Apple's site. They've put a lot of effort to migrate developers to this technology, had a tons of great WWDC videos about the topic, even some nice Interface Builder tutorial how to work with constraints.

There are many more tutorial sites, but probably Ray has the best Auto Layout tutorials out there:

So as you can see, we are getting close to support multiple device sizes. Now we have a lot of different screens and it would be horrible to calculate frames and positions manually.

Now instead of calculating things, you have to figure out how to create your constraints. There are priorities, and attributes, and many more... it's kinda confusing. That's why Apple created the constraint visual format language. Actually this VFL is so bad that I don't even want to demo it with Mr Circle.

VFL = WTF?

I am going to show this only once with Mr Triangle.

override func viewDidLoad() {
	super.viewDidLoad()
	
	self.view.backgroundColor = UIColor(hex: 0xeeeeee)
		
	let triangle = UIView(frame: CGRectZero)
	triangle.translatesAutoresizingMaskIntoConstraints = false
	triangle.backgroundColor = self.triangleColor
		
	let trianglePath = UIBezierPath()
	trianglePath.moveToPoint(CGPoint(x: 32, y: 0))
	trianglePath.addLineToPoint(CGPoint(x: 64, y: 64))
	trianglePath.addLineToPoint(CGPoint(x: 0, y: 64))
	trianglePath.closePath()
		
	let triangleMask    = CAShapeLayer()
	triangleMask.frame  = triangle.bounds
	triangleMask.path   = trianglePath.CGPath
	triangle.layer.mask = triangleMask
		
	self.view.addSubview(triangle)
		
	let vertical = NSLayoutConstraint.constraintsWithVisualFormat(
"V:[view]-(<=1)-[subview(==64)]",
options: .AlignAllCenterX,                                                      
metrics: nil,
views: ["view":self.view, "subview":triangle])

	let horizontal = NSLayoutConstraint.constraintsWithVisualFormat(
"H:[view]-(<=1)-[subview(==64)]",
options: .AlignAllCenterY,
metrics: nil,
views: ["view":self.view, "subview":triangle])
		
		self.view.addConstraints(vertical)
		self.view.addConstraints(horizontal)
        }

God forbid the engineer who invented this black magic. :) At least Mr Triangle is alive and safe. Don't worry we are moving forward to the next evolutionary step as soon as possible.

![](/content/images/2016/05/Simulator-Screen-Shot-2016--m-j--12--21-39-52.png)
![](/content/images/2016/05/Simulator-Screen-Shot-2016--m-j--12--21-39-54.png)

So as you can see we have a problem with constraints. The problem is that if you want to create complex constraints it's going to cost many many lines of code. This is why so many 3rd party frameworks came alive.

You can use any 3rd party library you want, or you can grab my snippet to make your life easier. Which leads us to our latest technology called: wait for it...

From the first day of auto layout, this constraint creation problem was with us. With my snippet I reduced the code as much as I could. For example I modified Mr Square's code, this is how Mr Rectangle came alive using my little tool.

override func viewDidLoad() {
	super.viewDidLoad()
		
	self.view.backgroundColor = UIColor(hex: 0xeeeeee)
		
	let rect = UIView(frame: CGRectZero)
	rect.translatesAutoresizingMaskIntoConstraints = false
	rect.backgroundColor = self.rectColor
		
	self.view.addSubview(rect)

	self.view.addConstraints([
		NSLayoutConstraint(rect, .Width, constant: 128),
		NSLayoutConstraint(rect, .Height, constant: 64),
		NSLayoutConstraint(rect, .CenterX, to: self.view),
		NSLayoutConstraint(rect, .CenterY, to: self.view),
	])
}
![](/content/images/2016/05/Simulator-Screen-Shot-2016--m-j--13--0-00-30.png)
![](/content/images/2016/05/Simulator-Screen-Shot-2016--m-j--13--0-00-32.png)

I even managed to have helper functions in a UIView extension, so let's tweak this shape a little bit more with some methods like these:

extension UIView
{
	
	convenience init(autolayout: Bool) {
		self.init(frame: CGRectZero)
		self.translatesAutoresizingMaskIntoConstraints = !autolayout
	}
	
	public class func create() -> Self {
		let _self = self.init()
		let view  = _self as UIView
		view.translatesAutoresizingMaskIntoConstraints = false
		return _self
	}
	
	public func widthConstraint(constant: CGFloat) {
		self.addConstraint(NSLayoutConstraint(self, .Width, constant: constant))
	}
	
	public func heightConstraint(constant: CGFloat) {
		self.addConstraint(NSLayoutConstraint(self, .Height, constant: constant))
	}
	
	public func centerConstraint(view: UIView) {
		view.addConstraints([
			NSLayoutConstraint(view, .CenterX, to: self),
			NSLayoutConstraint(view, .CenterY, to: self),
		])
	}
	
}

Now the actual code is going to be smoother like a circle, and shaped like a rectangle. Yep, Mr Oval has just joined to the party. :)

	override func viewDidLoad() {
		super.viewDidLoad()
		
		self.view.backgroundColor = UIColor(hex: 0xeeeeee)
		
		let oval = UIView.create()
		oval.backgroundColor = self.ovalColor
		
		let ovalMask    = CAShapeLayer()
		ovalMask.frame  = oval.bounds
		ovalMask.path   = CGPathCreateWithEllipseInRect(CGRect(x: 8, y: 0, width: 48, height: 64), nil)
		oval.layer.mask = ovalMask
		
		self.view.addSubview(oval)
		
		oval.widthConstraint(64)
		oval.heightConstraint(64)
		oval.centerConstraint(self.view)

![](/content/images/2016/05/Simulator-Screen-Shot-2016--m-j--13--0-09-54.png)
![](/content/images/2016/05/Simulator-Screen-Shot-2016--m-j--13--0-09-57.png)

So as you can see with this method I was pretty close to a nice layout engine, and the guys at Apple were doing some fine tunes under the hood as well. The result of this tuning is a new system - end of waiting - called:

Anchors

Anchors were born because auto layout had some construction flaws.

The NSLayoutAnchor class is a factory class for creating NSLayoutConstraint objects using a fluent API. Use these constraints to programatically define your layout using Auto Layout.

Developers tried to create their own builder / factory solutions, and - I think - I was pretty close to Anchors with my snippet. For example when Anchors came into the game it took only 1 hour to modify my UIView+AutoLayout extension class to run on Anchors instead of NSLayoutConstraint elements.

Nowadays I am not using xibs or Interface Builder anymore, because I have a flexible and fast layout system which conforms to the latest standards. In the future I'll share the UIView extension class, but right now it's not 100% ready. It serves my needs completely but I have to test it just a little more.

Ok I know, I know, too much talk. So here is the king of the layout technologies with a demo just for you, welcome Mr Star.

override func viewDidLoad() {
	super.viewDidLoad()
		
	self.view.backgroundColor = UIColor(hex: 0xeeeeee)
		
	let star = UIView(frame: CGRectZero)
	star.translatesAutoresizingMaskIntoConstraints = false
	star.backgroundColor = self.starColor
		
	let starPath = UIBezierPath()
	starPath.moveToPoint(CGPoint(x: 32, y: 0))
	starPath.addLineToPoint(CGPoint(x: 41.6, y: 15.37))
	starPath.addLineToPoint(CGPoint(x: 59.71, y: 16))
	starPath.addLineToPoint(CGPoint(x: 51.2, y: 32))
	starPath.addLineToPoint(CGPoint(x: 59.71, y: 48))
	starPath.addLineToPoint(CGPoint(x: 41.6, y: 48.63))
	starPath.addLineToPoint(CGPoint(x: 32, y: 64))
	starPath.addLineToPoint(CGPoint(x: 22.4, y: 48.63))
	starPath.addLineToPoint(CGPoint(x: 4.29, y: 48))
	starPath.addLineToPoint(CGPoint(x: 12.8, y: 32))
	starPath.addLineToPoint(CGPoint(x: 4.29, y: 16))
	starPath.addLineToPoint(CGPoint(x: 22.4, y: 15.37))
	starPath.closePath()
		
	let starMask        = CAShapeLayer()
	starMask.frame      = star.bounds
	starMask.path       = starPath.CGPath
	star.layer.mask = starMask
		
	self.view.addSubview(star)
		
	star.widthAnchor.constraintEqualToConstant(64).active = true
	star.heightAnchor.constraintEqualToConstant(64).active = true
star.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor).active = true
star.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor).active = true

}

As a closing I'd like to suggest to read this document about Anchors. Totally rocks. Even though I have my own wrapper around the layout engine, Anchors are the best thing for developers, so I highly recommend to learn and use them in the future. I hope this article helps you understand where things started and where we are now and in a month we'll see what the future brings for us. :)

PS. I know the shapes with the paths are not going to work in a real world, and this code would have some retain cycles, but this is just a demo. The idea of the different shapes came from this YouTube song. Enjoy. :)