Programmatically creating Autolayout constraints is famously dense to read, although the strongly-typed visual format makes some easier to parse visually.
With that in mind, which is easier to read?
This:
let subView1 = UIView()
let subView2 = UIView
let constraint = NSLayoutConstraint(item: subView1, attribute: .left, relatedBy: NSLayoutRelation.equal, toItem: subView2, attribute: .right, multiplier: 1.0, constant: -10)
constraint.priority = 900.0
view.addConstraint(constraint)
Or this:1
view.📌(subView1 ⬇️=⬆️ subView2 + 10 ‼️ 900.0)
I wrote this as an exercise after reading a description of Erica Sadun’s talk and some posts of hers on the emoji as function name idea. I happened to read this around the time that I was needing to layout a number of views in code using autolayout and was overwhelmed with the verbosity.
So I started goofing. Basically, I use the “boxed arrow” emoji to indicate which edge is being constrained, and double arrows to indicate height/width. Then and “=” operator to indicate that two edges are constrained.
The “+” operator is overloaded to add a constant to the constraint, and the “‼️” emoji is overloaded to indicate priority. Sadly, I couldn’t find an emoji that was valid for use as an operator and that was as clear as the 📌 for communicating “add constraint, so I named a function “📌”.
Lastly, I probably could have just overloaded “+” again to add the constraints, but I ran out of time and it was simpler to overload ++ for adding different constraints.
Likewise, it would be useful to add “<=”, “>=” operators.
Anyway, I’m pretty happy with this fun project. I think that the example below demonstrates the use the syntax. I have also embedded my full playground as a gist.

and here is the layout code
// add one constraint at a time
view.📌(grayView |⬅️ 20.0) //to superview margin
view.📌(grayView -⬆️ 20.0)
view.📌(grayView ⬇️- 20.0)
view.📌(grayView ➡️| 20.0)
// add multiple constraints (combined with ++ operator)
view.📌(redButton |⬅️ 60 ++ redButton -⬆️ 60 ++ redButton ↕️= 40 ++ redButton ↔️= 40)
// can add constants
view.📌(redButton ↔️=↔️ blueButton + 20)
// can add priorities
view.📌(redButton ⬇️=⬆️ blueButton + 100 ‼️ 800.0)
view.📌(redButton ⬇️=⬆️ blueButton + 10 ‼️ 900.0)
view.📌(redButton |=| blueButton) //align vertical centers
view.📌(blueButton ↕️= 100)
view.📌((blueButton--orangeButton + 40) ++ (blueButton-=-orangeButton)) //precedence rules require parentheses around constraints that have constants or priorities
orangeButton.📌(orangeButton ↕️= 45 ++ orangeButton ↔️= 25)
complete gist:
//: Playground – noun: a place where people can play | |
import UIKit | |
import PlaygroundSupport | |
///create a constraint of view to its superView | |
infix operator ➡️|: AdditionPrecedence | |
infix operator |⬅️: AdditionPrecedence | |
infix operator -⬆️: AdditionPrecedence | |
infix operator ⬇️-: AdditionPrecedence | |
///create a constraint between the designated edge of view to the designated edge of another view | |
infix operator ➡️=⬅️: TernaryPrecedence | |
infix operator –: TernaryPrecedence //same as ➡️=⬅️. it's just clearer | |
infix operator ⬇️=⬆️: TernaryPrecedence | |
infix operator ⬆️=⬆️: TernaryPrecedence | |
infix operator ⬇️=⬇️: TernaryPrecedence | |
infix operator ➡️=➡️: TernaryPrecedence | |
infix operator ⬅️=⬅️: TernaryPrecedence | |
infix operator ↔️=↔️: TernaryPrecedence | |
infix operator ↕️=↕️: TernaryPrecedence | |
///create a constraint between horizontal centers of two views | |
infix operator -=-: TernaryPrecedence | |
///create a constraint between vertical centers of two views | |
infix operator |=|: TernaryPrecedence | |
///create a width constraint on a view | |
infix operator ↔️=: AdditionPrecedence | |
///create a height constraint on a view | |
infix operator ↕️=: AdditionPrecedence | |
///constrain every edge a view to another view | |
infix operator ⏹=⏹: AdditionPrecedence | |
extension UIView { | |
///add a constraint of view to its superView | |
func 📌<T: Any>(_ oneOrMany: T) { | |
if let one = oneOrMany as? NSLayoutConstraint { | |
addConstraint(one) | |
} else if let many = oneOrMany as? [NSLayoutConstraint] { | |
addConstraints(many) | |
} else { | |
print(oneOrMany.self) | |
} | |
} | |
} | |
infix operator +: AdditionPrecedence | |
func +(left: UIView, right: CGFloat) -> ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint) { | |
let returnBlock = {(view, attribute1, attribute2) -> NSLayoutConstraint in | |
return NSLayoutConstraint(item: view, attribute: attribute1, relatedBy: NSLayoutRelation.equal, toItem: left, attribute: attribute2, multiplier: 1.0, constant: -right) | |
} | |
return returnBlock | |
} | |
func +(left: UIView, right: (CGFloat, CGFloat)) -> ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint) { | |
let returnBlock: ((UIView, NSLayoutAttribute, NSLayoutAttribute) -> NSLayoutConstraint) = {(view, attribute1, attribute2) -> NSLayoutConstraint in | |
let constraint = NSLayoutConstraint(item: view, attribute: attribute1, relatedBy: NSLayoutRelation.equal, toItem: left, attribute: attribute2, multiplier: 1.0, constant: -right.0) | |
constraint.priority = UILayoutPriority(right.1) | |
return constraint | |
} | |
return returnBlock | |
} | |
infix operator ++: TernaryPrecedence | |
func ++(left: NSLayoutConstraint, right: NSLayoutConstraint) -> [NSLayoutConstraint] { | |
return [left, right] | |
} | |
func ++(left: NSLayoutConstraint, right: [NSLayoutConstraint]) -> [NSLayoutConstraint] { | |
return [left] + right | |
} | |
func ++(left: [NSLayoutConstraint], right: [NSLayoutConstraint]) -> [NSLayoutConstraint] { | |
return left + right | |
} | |
func ++(left: [NSLayoutConstraint], right: NSLayoutConstraint) -> [NSLayoutConstraint] { | |
return left + [right] | |
} | |
func ++<T: Any>(_ left: T, right: T) -> [NSLayoutConstraint] { | |
if let l = left as? NSLayoutConstraint { | |
if let r = right as? NSLayoutConstraint { | |
return [l, r] | |
} else if let r = right as? [NSLayoutConstraint] { | |
return [l] + r | |
} | |
} else if let l = left as? [NSLayoutConstraint] { | |
if let r = right as? NSLayoutConstraint { | |
return l + [r] | |
} else if let r = right as? [NSLayoutConstraint] { | |
return l + r | |
} | |
} | |
fatalError() | |
} | |
infix operator ‼️: MultiplicationPrecedence | |
func ‼️(left: CGFloat, right: Double) -> (CGFloat, CGFloat) { | |
return (left, CGFloat(right)) | |
} | |
infix operator -: AdditionPrecedence | |
func -(left: UIView, right: CGFloat) -> ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint) { | |
let returnBlock = {(view, attribute1, attribute2) -> NSLayoutConstraint in | |
return NSLayoutConstraint(item: view, attribute: attribute1, relatedBy: NSLayoutRelation.equal, toItem: left, attribute: attribute2, multiplier: 1.0, constant: right) | |
} | |
return returnBlock | |
} | |
func -(left: UIView, right: (CGFloat, CGFloat)) -> ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint) { | |
let returnBlock: ((UIView, NSLayoutAttribute, NSLayoutAttribute) -> NSLayoutConstraint) = {(view, attribute1, attribute2) -> NSLayoutConstraint in | |
let constraint = NSLayoutConstraint(item: view, attribute: attribute1, relatedBy: NSLayoutRelation.equal, toItem: left, attribute: attribute2, multiplier: 1.0, constant: right.0) | |
constraint.priority = UILayoutPriority(right.1) | |
return constraint | |
} | |
return returnBlock | |
} | |
func ➡️| (left: UIView, right: CGFloat) -> NSLayoutConstraint { | |
return NSLayoutConstraint.constraints(withVisualFormat: "H:[left]-\(right)-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["left": left]).first! | |
} | |
func |⬅️ (left: UIView, right: CGFloat) -> NSLayoutConstraint { | |
return NSLayoutConstraint.constraints(withVisualFormat: "H:|-\(right)-[left]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["left": left]).first! | |
} | |
func -⬆️ (left: UIView, right: CGFloat) -> NSLayoutConstraint { | |
return NSLayoutConstraint.constraints(withVisualFormat: "V:|-\(right)-[left]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["left": left]).first! | |
} | |
func ⬇️- (left: UIView, right: CGFloat) -> NSLayoutConstraint { | |
return NSLayoutConstraint.constraints(withVisualFormat: "V:[left]-\(right)-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["left": left]).first! | |
} | |
func ➡️=⬅️ (left: UIView, right: UIView) -> NSLayoutConstraint { | |
return left ➡️=⬅️ right + 0 | |
} | |
func ➡️=⬅️ (left: UIView, right: ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint)) -> NSLayoutConstraint { | |
return right(left, .trailing, .leading) | |
} | |
func –(left: UIView, right: UIView) -> NSLayoutConstraint {//it's just too obvious not to include it | |
return left ➡️=⬅️ right + 0 | |
} | |
func –(left: UIView, right: ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint)) -> NSLayoutConstraint { | |
return right(left, .trailing, .leading) | |
} | |
func ⬇️=⬆️ (left: UIView, right: UIView) -> NSLayoutConstraint { | |
return left ⬇️=⬆️ right + 0 | |
} | |
func ⬇️=⬆️ (left: UIView, right: ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint)) -> NSLayoutConstraint { | |
return right(left, .bottom, .top) | |
} | |
func ⬆️=⬆️ (left: UIView, right: UIView) -> NSLayoutConstraint { | |
return left ⬆️=⬆️ right + 0 | |
} | |
func ⬆️=⬆️ (left: UIView, right: ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint)) -> NSLayoutConstraint { | |
return right(left, .top, .top) | |
} | |
func ⬇️=⬇️ (left: UIView, right: UIView) -> NSLayoutConstraint { | |
return left ⬇️=⬇️ (right + 0) | |
} | |
func ⬇️=⬇️ (left: UIView, right: ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint)) -> NSLayoutConstraint { | |
return right(left, .bottom, .bottom) | |
} | |
func ➡️=➡️ (left: UIView, right: UIView) -> NSLayoutConstraint { | |
return left ➡️=➡️ (right + 0) | |
} | |
func ➡️=➡️ (left: UIView, right: ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint)) -> NSLayoutConstraint { | |
return right(left, .right, .right) | |
} | |
func ⬅️=⬅️ (left: UIView, right: UIView) -> NSLayoutConstraint { | |
return left ⬅️=⬅️ (right + 0) | |
} | |
func ⬅️=⬅️ (left: UIView, right: ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint)) -> NSLayoutConstraint { | |
return right(left, .left, .left) | |
} | |
func ↔️=↔️ (left: UIView, right: UIView) -> NSLayoutConstraint { | |
return left ↔️=↔️ right + 0 | |
} | |
func ↔️=↔️(left: UIView, right: ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint)) -> NSLayoutConstraint { | |
return right(left, .width, .width) | |
} | |
func ↕️=↕️(left: UIView, right: UIView) -> NSLayoutConstraint { | |
return left ↕️=↕️ right + 0 | |
} | |
func ↕️=↕️ (left: UIView, right: ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint)) -> NSLayoutConstraint { | |
return right(left, .height, .height) | |
} | |
func -=-(left: UIView, right: UIView) -> NSLayoutConstraint { | |
return left -=- right + 0 | |
} | |
func -=- (left: UIView, right: ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint)) -> NSLayoutConstraint { | |
return right(left, .centerY, .centerY) | |
} | |
func |=|(left: UIView, right: UIView) -> NSLayoutConstraint { | |
return left |=| right + 0 | |
} | |
func |=| (left: UIView, right: ((UIView, NSLayoutAttribute, NSLayoutAttribute) ->NSLayoutConstraint)) -> NSLayoutConstraint { | |
return right(left, .centerX, .centerX) | |
} | |
func ↔️= (view: UIView, width: CGFloat) -> NSLayoutConstraint { | |
return NSLayoutConstraint.constraints(withVisualFormat: "H:[view(==\(width))]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["view": view]).first! | |
} | |
func ↕️= (view: UIView, height: CGFloat) -> NSLayoutConstraint { | |
return NSLayoutConstraint.constraints(withVisualFormat: "V:[view(==\(height))]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["view": view]).first! | |
} | |
func ⏹=⏹ (left: UIView, right: UIView) -> [NSLayoutConstraint] { | |
return [ | |
left ⬆️=⬆️ right, | |
left ⬇️=⬇️ right, | |
left ⬅️=⬅️ right, | |
left ➡️=➡️ right, | |
] | |
} | |
//transforms ↩️↪️ | |
//animations?🔂 | |
let containerView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 375.0, height: 300)) | |
PlaygroundPage.current.liveView = containerView | |
containerView.backgroundColor = .brown | |
let redButton = UIButton() | |
redButton.backgroundColor = .red | |
redButton.setTitleColor(.blue, for: .normal) | |
redButton.setTitleColor(.blue, for: .selected) | |
redButton.translatesAutoresizingMaskIntoConstraints = false | |
let blueButton = UIButton() | |
blueButton.translatesAutoresizingMaskIntoConstraints = false | |
blueButton.backgroundColor = .blue | |
let orangeButton = UIButton() | |
orangeButton.translatesAutoresizingMaskIntoConstraints = false | |
orangeButton.backgroundColor = .orange | |
let grayView = UIView() | |
grayView.translatesAutoresizingMaskIntoConstraints = false | |
grayView.backgroundColor = .lightGray | |
containerView.addSubview(grayView) | |
containerView.addSubview(redButton) | |
containerView.addSubview(blueButton) | |
containerView.addSubview(orangeButton) | |
containerView.📌(grayView |⬅️ 20.0) | |
containerView.📌(grayView -⬆️ 20.0) | |
containerView.📌(grayView ⬇️- 20.0) | |
containerView.📌(grayView ➡️| 20.0) | |
containerView.📌(redButton |⬅️ 60 ++ redButton -⬆️ 60 ++ redButton ↕️= 40 ++ redButton ↔️= 40) | |
containerView.📌(redButton ⬇️=⬆️ blueButton + 100 ‼️ 800.0) | |
containerView.📌(redButton ⬇️=⬆️ blueButton + 10 ‼️ 900.0) //overrides previous due to precedence | |
containerView.📌(redButton |=| blueButton) //align vertical centers | |
containerView.📌(redButton ↔️=↔️ blueButton + 20) | |
containerView.📌(blueButton ↕️= 100) | |
containerView.📌((blueButton–orangeButton + 40) ++ (blueButton-=-orangeButton)) //precedence requires parentheses around constraints with other operators | |
orangeButton.📌(orangeButton ↕️= 45 ++ orangeButton ↔️= 25) |
- I certainly agree that the advantage is purely in readability. typing these would only be feasible with a series of textExpander/Xcode snippets ↩︎