Skip to content

Instantly share code, notes, and snippets.

@DanielCardonaRojas
Last active November 15, 2019 18:20
Show Gist options
  • Save DanielCardonaRojas/91ba7adbb7b0cbe9d38cd525640e5c4f to your computer and use it in GitHub Desktop.
Save DanielCardonaRojas/91ba7adbb7b0cbe9d38cd525640e5c4f to your computer and use it in GitHub Desktop.
Declarative KeyPath based Autolayout combinators
//
// KeyPathAutoLayout.swift
// Spotit
//
// Created by Daniel Cardona Rojas on 14/11/19.
// Copyright © 2019 Daniel Cardona Rojas. All rights reserved.
//
import UIKit
public class Constraint {
typealias ConstraintBuilder = (UIView, UIView) -> NSLayoutConstraint
public typealias Configuration = [Constraint]
private var constraint: ConstraintBuilder
init(_ constraint: @escaping ConstraintBuilder) {
self.constraint = constraint
}
@discardableResult
func resolve(_ view1: UIView, _ view2: UIView) -> NSLayoutConstraint {
let layoutConstraint = constraint(view1, view2)
return layoutConstraint
}
@discardableResult
public static func resolveConstraints(_ view1: UIView, _ view2: UIView, constraints: [Constraint]) -> [NSLayoutConstraint] {
let layoutConstraints = constraints.map { (c: Constraint) -> NSLayoutConstraint in
let layoutConstraint = c.resolve(view1, view2)
return layoutConstraint
}
return layoutConstraints
}
}
extension NSLayoutConstraint {
static func activating(_ constraints: [[NSLayoutConstraint]]) {
let cons = constraints.flatMap({ $0 })
NSLayoutConstraint.activate(cons)
}
}
// MARK: - Constraint Primitives: Axis
public func equal<L, Axis>(_ to: KeyPath<UIView, L>, constant: CGFloat = 0.0) -> Constraint where L: NSLayoutAnchor<Axis> {
return Constraint { view1, view2 in
view1[keyPath: to].constraint(equalTo: view2[keyPath: to], constant: constant)
}
}
public func equal<L, Axis>(_ from: KeyPath<UIView, L>, _ to: KeyPath<UIView, L>, constant: CGFloat = 0) -> Constraint where L: NSLayoutAnchor<Axis> {
return Constraint { view1, view2 in
view1[keyPath: from].constraint(equalTo: view2[keyPath: to], constant: constant)
}
}
// MARK: - Constraint Primitives: Dimensions
public func equalToConstant<L>(_ keyPath: KeyPath<UIView, L>, constant: CGFloat) -> Constraint where L: NSLayoutDimension {
return Constraint { view1, _ in
view1[keyPath: keyPath].constraint(equalToConstant: constant)
}
}
public func equal<L>(_ from: KeyPath<UIView, L>, _ to: KeyPath<UIView, L>, multiplier: CGFloat = 1, constant: CGFloat = 0) -> Constraint where L: NSLayoutDimension {
return Constraint { view1, view2 in
let dim1 = view1[keyPath: from]
let dim2 = view2[keyPath: to]
return dim1.constraint(equalTo: dim2, multiplier: multiplier, constant: constant)
}
}
// MARK: - Common Configurations
extension Constraint.Configuration {
public static func bottomRight(rightMargin: CGFloat = 0, bottomMargin: CGFloat = 0) -> [Constraint] {
[equal(\.bottomAnchor, constant: -bottomMargin),
equal(\.rightAnchor, constant: -rightMargin),
]
}
public static func bottomLeft(leftMargin: CGFloat = 0, bottomMargin: CGFloat = 0) -> [Constraint] {
[
equal(\.bottomAnchor, constant: -bottomMargin),
equal(\.leftAnchor, constant: leftMargin),
]
}
public static func topLeft(leftMargin: CGFloat = 0, topMargin: CGFloat = 0) -> [Constraint] {
[
equal(\.topAnchor, constant: topMargin),
equal(\.leftAnchor, constant: leftMargin),
]
}
public static func topRight(rightMargin: CGFloat = 0, topMargin: CGFloat = 0) -> [Constraint] {
[
equal(\.topAnchor, constant: topMargin),
equal(\.rightAnchor, constant: -rightMargin),
]
}
public static func inset(by padding: CGFloat) -> [Constraint] {
[
equal(\.topAnchor, constant: padding),
equal(\.rightAnchor, constant: -padding),
equal(\.leftAnchor, constant: padding),
equal(\.bottomAnchor, constant: -padding),
]
}
public static var centered: [Constraint] {
[
equal(\.centerYAnchor),
equal(\.centerXAnchor),
]
}
public static func leftOf(spacing: CGFloat = 0) -> [Constraint] {
[equal(\.rightAnchor, \.leftAnchor, constant: -spacing)]
}
public static func rightOf(spacing: CGFloat = 0) -> [Constraint] {
[equal(\.leftAnchor, \.rightAnchor, constant: spacing)]
}
public static func below(spacing: CGFloat = 0) -> [Constraint] {
[equal(\.topAnchor, \.bottomAnchor, constant: spacing)]
}
public static func above(spacing: CGFloat = 0) -> [Constraint] {
[equal(\.bottomAnchor, \.topAnchor, constant: -spacing)]
}
public static func equallySized() -> [Constraint] {
[equal(\.widthAnchor), equal(\.heightAnchor)]
}
public static func centerY(offset: CGFloat = 0) -> [Constraint] {
[equal(\.centerYAnchor, constant: offset)]
}
public static func centerX(offset: CGFloat = 0) -> [Constraint] {
[equal(\.centerXAnchor, constant: offset)]
}
// MARK: - Self applying combinators
public static func height(_ height: CGFloat) -> [Constraint] {
[equalToConstant(\.heightAnchor, constant: height)]
}
public static func width(_ height: CGFloat) -> [Constraint] {
[equalToConstant(\.widthAnchor, constant: height)]
}
public static func aspectRatio(_ ratio: CGFloat) -> [Constraint] {
[equal(\.heightAnchor, \.widthAnchor, multiplier: ratio, constant: 0)]
}
}
// MARK: - Generic
extension UIView {
func relativeTo(_ view: UIView, positionBy constraints: [Constraint]) -> [NSLayoutConstraint] {
return Constraint.resolveConstraints(self, view, constraints: constraints)
}
func constrainedBy(_ constraints: [Constraint]) -> [NSLayoutConstraint] {
return Constraint.resolveConstraints(self, self, constraints: constraints)
}
}
// MARK: - Self referencing combinators
extension UIView {
public func heightToWidthRatio(_ ratio: CGFloat) -> NSLayoutConstraint {
let ratioConstraint = equal(\.heightAnchor, \.widthAnchor, multiplier: ratio).resolve(self, self)
return ratioConstraint
}
public func widthToHeightRatio(_ ratio: CGFloat) -> NSLayoutConstraint {
let ratioConstraint = equal(\.heightAnchor, \.widthAnchor, multiplier: ratio).resolve(self, self)
return ratioConstraint
}
public var squared: NSLayoutConstraint {
return widthToHeightRatio(1.0)
}
public func constantHeight(_ height: CGFloat) -> NSLayoutConstraint {
return heightAnchor.constraint(equalToConstant: height)
}
public func constantWidth(_ width: CGFloat) -> NSLayoutConstraint {
return widthAnchor.constraint(equalToConstant: width)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment