Last active
January 26, 2017 23:12
-
-
Save chrisbrandow/a13342b0ca487b2613eaa06a8ba4c4d8 to your computer and use it in GitHub Desktop.
A set of operators to make Autolayout extremely readable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: 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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment