Instantly share code, notes, and snippets.

Embed
What would you like to do?
Mini Auto Layout DSL revisited
import UIKit
import PlaygroundSupport
typealias Constraint = (_ view: UIView, _ otherView: UIView?) -> NSLayoutConstraint
enum ConstraintRelation {
case equal, greaterThanOrEqual, lessThanOrEqual
}
func constraint<Anchor, AnchorType>(_ keyPath: KeyPath<UIView, Anchor>,
_ otherKeyPath: KeyPath<UIView, Anchor>? = nil,
constraintRelation: ConstraintRelation = .equal,
multiplier: CGFloat? = nil,
constant: CGFloat = 0,
priority: UILayoutPriority? = nil) -> Constraint where Anchor: NSLayoutAnchor<AnchorType> {
return { view, otherView in
guard let otherView = otherView else { fatalError("Pairing view missing")}
var partialConstraint: NSLayoutConstraint
let otherKeyPath = otherKeyPath ?? keyPath
switch constraintRelation {
case .equal:
partialConstraint = view[keyPath: keyPath].constraint(equalTo: otherView[keyPath: otherKeyPath], constant: constant)
case .greaterThanOrEqual:
partialConstraint = view[keyPath: keyPath].constraint(greaterThanOrEqualTo: otherView[keyPath: otherKeyPath], constant: constant)
case .lessThanOrEqual:
partialConstraint = view[keyPath: keyPath].constraint(lessThanOrEqualTo: otherView[keyPath: otherKeyPath], constant: constant)
}
return fullConstraint(from: partialConstraint, withMultiplier:multiplier, priority: priority)
}
}
func constraint<Anchor>(_ keyPath: KeyPath<UIView, Anchor>,
_ otherKeyPath: KeyPath<UIView, Anchor>? = nil,
constraintRelation: ConstraintRelation = .equal,
multiplier: CGFloat? = nil,
constant: CGFloat = 0,
priority: UILayoutPriority? = nil) -> Constraint where Anchor: NSLayoutDimension {
return { view, otherView in
func constraint(otherView: UIView,
otherKeyPath: KeyPath<UIView, Anchor>?) -> NSLayoutConstraint {
if let otherKeyPath = otherKeyPath {
switch constraintRelation {
case .equal:
return view[keyPath: keyPath].constraint(equalTo:otherView[keyPath: otherKeyPath], constant: constant)
case .greaterThanOrEqual:
return view[keyPath: keyPath].constraint(greaterThanOrEqualTo:otherView[keyPath: otherKeyPath], constant: constant)
case .lessThanOrEqual:
return view[keyPath: keyPath].constraint(lessThanOrEqualTo:otherView[keyPath: otherKeyPath], constant: constant)
}
} else {
switch constraintRelation {
case .equal:
return view[keyPath: keyPath].constraint(equalToConstant: constant)
case .greaterThanOrEqual:
return view[keyPath: keyPath].constraint(greaterThanOrEqualToConstant: constant)
case .lessThanOrEqual:
return view[keyPath: keyPath].constraint(lessThanOrEqualToConstant: constant)
}
}
}
return fullConstraint(from: constraint(otherView: otherView ?? view,
otherKeyPath: otherView == nil ? otherKeyPath : otherKeyPath ?? keyPath),
withMultiplier:multiplier,
priority: priority)
}
}
func fullConstraint(from partialConstraint: NSLayoutConstraint,
withMultiplier multiplier: CGFloat? = nil,
priority: UILayoutPriority?) -> NSLayoutConstraint {
var constraint = partialConstraint
if let multiplier = multiplier {
constraint = NSLayoutConstraint(item: constraint.firstItem as Any,
attribute: constraint.firstAttribute,
relatedBy: constraint.relation,
toItem: constraint.secondItem,
attribute: constraint.secondAttribute,
multiplier: multiplier,
constant: constraint.constant)
}
if let priority = priority {
constraint.priority = priority
}
return constraint
}
extension UIView {
func addSubview(_ child: UIView, pairingTo pairingView: UIView? = nil, constraints: [Constraint]) {
addSubview(child)
child.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(constraints.map { $0(child, pairingView ?? self) })
}
func constrainToView(_ pairingView: UIView, constraints: [Constraint]) {
NSLayoutConstraint.activate(constraints.map { $0(self, pairingView) })
}
func constrain(to constraints: [Constraint]) {
NSLayoutConstraint.activate(constraints.map { $0(self, nil) })
}
}
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.backgroundColor = .yellow
label.textColor = .black
label.textAlignment = .center
label.text = "Hello World!"
let greenView = UIView()
greenView.backgroundColor = .green
view.addSubview(greenView, constraints: [
constraint(\.leftAnchor, constant: 20.0, priority: .required),
constraint(\.rightAnchor, constant: -20.0),
constraint(\.topAnchor, constant: 220.0),
constraint(\.bottomAnchor, constant: -20.0)
])
view.addSubview(label, pairingTo:greenView, constraints: [
constraint(\.centerXAnchor),
constraint(\.centerYAnchor),
])
let purpleView = UIView()
purpleView.backgroundColor = .purple
view.addSubview(purpleView, pairingTo:greenView, constraints: [
constraint(\.leftAnchor, \.centerXAnchor),
])
purpleView.constrainToView(label, constraints: [
constraint(\.widthAnchor, constraintRelation: .greaterThanOrEqual, multiplier: 0.5, priority: .defaultHigh),
constraint(\.topAnchor, \.bottomAnchor, constant: 10.0, priority: UILayoutPriority(500))
])
label.constrain(to: [
constraint(\.widthAnchor, constant: 250.0)
])
purpleView.constrain(to: [
constraint(\.heightAnchor, constant: 60.0, priority: .defaultLow),
constraint(\.widthAnchor, \.heightAnchor)
])
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment