Skip to content

Instantly share code, notes, and snippets.

@SzymonMrozek
Last active November 28, 2019 18:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SzymonMrozek/ee5c929b0410db95103e806cac108e66 to your computer and use it in GitHub Desktop.
Save SzymonMrozek/ee5c929b0410db95103e806cac108e66 to your computer and use it in GitHub Desktop.
Set of useful operators for more readable creation of constraints
import UIKit
import Foundation
infix operator ~ : RangeFormationPrecedence
public enum AnchorOption: Hashable {
case priority(UILayoutPriority)
case multiplier(CGFloat)
case constant(CGFloat)
public static func == (lhs: AnchorOption, rhs: AnchorOption) -> Bool {
switch (lhs, rhs) {
case (.priority, .priority): return true
case (.constant, .constant): return true
case (.multiplier, .multiplier): return true
default: return false
}
}
}
public struct Anchor<T: AnyObject> {
let anchor: NSLayoutAnchor<T>
let options: Set<AnchorOption>
fileprivate func appendingOption(_ option: AnchorOption) -> Anchor<T> {
var newOptions = self.options
guard newOptions.insert(option).inserted else { fatalError("Duplication of options") }
return Anchor<T>(anchor: self.anchor, options: newOptions)
}
}
extension Set where Element == AnchorOption {
fileprivate var constant: CGFloat? {
return compactMap {
guard case .constant(let c) = $0 else { return nil }
return c
}.first
}
fileprivate var priority: UILayoutPriority? {
return compactMap {
guard case .priority(let p) = $0 else { return nil }
return p
}.first
}
fileprivate var multiplier: CGFloat? {
return compactMap {
guard case .multiplier(let m) = $0 else { return nil }
return m
}.first
}
}
// Anchor producers operators
public func + <T: AnyObject>(lhs: NSLayoutAnchor<T>, rhs: CGFloat) -> Anchor<T> {
return Anchor(anchor: lhs, options: [.constant(rhs)])
}
public func - <T: AnyObject>(lhs: NSLayoutAnchor<T>, rhs: CGFloat) -> Anchor<T> {
return Anchor(anchor: lhs, options: [.constant(-rhs)])
}
public func * (lhs: NSLayoutAnchor<NSLayoutDimension>, rhs: CGFloat) -> Anchor<NSLayoutDimension> {
return Anchor(anchor: lhs, options: [.multiplier(rhs)])
}
public func ~ <T: AnyObject>(lhs: NSLayoutAnchor<T>, rhs: UILayoutPriority) -> Anchor<T> {
return Anchor(anchor: lhs, options: [.priority(rhs)])
}
public func ~ <T: AnyObject>(lhs: NSLayoutAnchor<T>, rhs: Float) -> Anchor<T> {
let priority = UILayoutPriority(rawValue: rhs)
return Anchor(anchor: lhs, options: [.priority(priority)])
}
// Anchor compositions operators
public func * (lhs: Anchor<NSLayoutDimension>, rhs: CGFloat) -> Anchor<NSLayoutDimension> {
return lhs.appendingOption(.multiplier(rhs))
}
public func + <T: AnyObject>(lhs: Anchor<T>, rhs: CGFloat) -> Anchor<T> {
return lhs.appendingOption(.constant(rhs))
}
public func ~ <T: AnyObject>(lhs: Anchor<T>, rhs: UILayoutPriority) -> Anchor<T> {
return lhs.appendingOption(.priority(rhs))
}
public func ~ <T: AnyObject>(lhs: Anchor<T>, rhs: Float) -> Anchor<T> {
let priority = UILayoutPriority(rawValue: rhs)
return lhs.appendingOption(.priority(priority))
}
// Anchor Options producers operators
public func ~ (lhs: Double, rhs: Double) -> Set<AnchorOption> {
return [.constant(CGFloat(lhs)), .priority(UILayoutPriority(Float(rhs)))]
}
public func ~ (lhs: CGFloat, rhs: UILayoutPriority) -> Set<AnchorOption> {
return [.constant(lhs), .priority(rhs)]
}
/// Dimension x Constant - Single
public func == (lhs: NSLayoutDimension, rhs: CGFloat) -> NSLayoutConstraint {
return lhs.constraint(equalToConstant: rhs)
}
public func >= (lhs: NSLayoutDimension, rhs: CGFloat) -> NSLayoutConstraint {
return lhs.constraint(greaterThanOrEqualToConstant: rhs)
}
public func <= (lhs: NSLayoutDimension, rhs: CGFloat) -> NSLayoutConstraint {
return lhs.constraint(lessThanOrEqualToConstant: rhs)
}
// Dimension x Constant + Priority
public func == (lhs: NSLayoutDimension, rhs: Set<AnchorOption>) -> NSLayoutConstraint {
guard let constant = rhs.constant else { fatalError("Invalid options composition") }
let constraint = lhs.constraint(equalToConstant: constant)
constraint.applyPriorityIfExist(fromOptions: rhs)
return constraint
}
public func >= (lhs: NSLayoutDimension, rhs: Set<AnchorOption>) -> NSLayoutConstraint {
guard let constant = rhs.constant else { fatalError("Invalid constaint patch") }
let constraint = lhs.constraint(greaterThanOrEqualToConstant: constant)
constraint.applyPriorityIfExist(fromOptions: rhs)
return constraint
}
public func <= (lhs: NSLayoutDimension, rhs: Set<AnchorOption>) -> NSLayoutConstraint {
guard let constant = rhs.constant else { fatalError("Invalid constaint patch") }
let constraint = lhs.constraint(lessThanOrEqualToConstant: constant)
constraint.applyPriorityIfExist(fromOptions: rhs)
return constraint
}
// Dimension x Constant [Array]
public func == (lhs: [NSLayoutDimension], rhs: CGFloat) -> [NSLayoutConstraint] {
return lhs.map { $0 == rhs }
}
public func <= (lhs: [NSLayoutDimension], rhs: CGFloat) -> [NSLayoutConstraint] {
return lhs.map { $0 <= rhs }
}
public func >= (lhs: [NSLayoutDimension], rhs: CGFloat) -> [NSLayoutConstraint] {
return lhs.map { $0 >= rhs }
}
// Dimension x Constant + Priority [Array]
public func == (lhs: [NSLayoutDimension], rhs: Set<AnchorOption>) -> [NSLayoutConstraint] {
return lhs.map { $0 == rhs }
}
public func >= (lhs: [NSLayoutDimension], rhs: Set<AnchorOption>) -> [NSLayoutConstraint] {
return lhs.map { $0 >= rhs }
}
public func <= (lhs: [NSLayoutDimension], rhs: Set<AnchorOption>) -> [NSLayoutConstraint] {
return lhs.map { $0 <= rhs }
}
// Anchor x Anchor
public func == <T: AnyObject>(lhs: NSLayoutAnchor<T>, rhs: NSLayoutAnchor<T>) -> NSLayoutConstraint {
return lhs.constraint(equalTo: rhs)
}
public func >= <T: AnyObject>(lhs: NSLayoutAnchor<T>, rhs: NSLayoutAnchor<T>) -> NSLayoutConstraint {
return lhs.constraint(greaterThanOrEqualTo: rhs)
}
public func <= <T: AnyObject>(lhs: NSLayoutAnchor<T>, rhs: NSLayoutAnchor<T>) -> NSLayoutConstraint {
return lhs.constraint(lessThanOrEqualTo: rhs)
}
// Anchor x Anchor + Options
public func == <T: AnyObject>(lhs: NSLayoutAnchor<T>, rhs: Anchor<T>) -> NSLayoutConstraint {
let constraint = lhs.constraint(equalTo: rhs.anchor, constant: rhs.options.constant ?? 0.0)
constraint.applyPriorityIfExist(fromOptions: rhs.options)
return constraint
}
public func >= <T: AnyObject>(lhs: NSLayoutAnchor<T>, rhs: Anchor<T>) -> NSLayoutConstraint {
let constraint = lhs.constraint(greaterThanOrEqualTo: rhs.anchor, constant: rhs.options.constant ?? 0.0)
constraint.applyPriorityIfExist(fromOptions: rhs.options)
return constraint
}
public func <= <T: AnyObject>(lhs: NSLayoutAnchor<T>, rhs: Anchor<T>) -> NSLayoutConstraint {
let constraint = lhs.constraint(lessThanOrEqualTo: rhs.anchor, constant: rhs.options.constant ?? 0.0)
constraint.applyPriorityIfExist(fromOptions: rhs.options)
return constraint
}
// Dimension x Dimension + Options
public func == (lhs: NSLayoutDimension, rhs: Anchor<NSLayoutDimension>) -> NSLayoutConstraint {
let constraint = lhs.constraint(equalTo: rhs.anchor as! NSLayoutDimension,
multiplier: rhs.options.multiplier ?? 1.0,
constant: rhs.options.constant ?? 0.0)
constraint.applyPriorityIfExist(fromOptions: rhs.options)
return constraint
}
public func >= (lhs: NSLayoutDimension, rhs: Anchor<NSLayoutDimension>) -> NSLayoutConstraint {
let constraint = lhs.constraint(greaterThanOrEqualTo: rhs.anchor as! NSLayoutDimension,
multiplier: rhs.options.multiplier ?? 1.0,
constant: rhs.options.constant ?? 0.0)
constraint.applyPriorityIfExist(fromOptions: rhs.options)
return constraint
}
public func <= (lhs: NSLayoutDimension, rhs: Anchor<NSLayoutDimension>) -> NSLayoutConstraint {
let constraint = lhs.constraint(greaterThanOrEqualTo: rhs.anchor as! NSLayoutDimension,
multiplier: rhs.options.multiplier ?? 1.0,
constant: rhs.options.constant ?? 0.0)
constraint.applyPriorityIfExist(fromOptions: rhs.options)
return constraint
}
// Helpers
extension NSLayoutConstraint {
fileprivate func applyPriorityIfExist(fromOptions options: Set<AnchorOption>) {
if let priority = options.priority {
self.priority = priority
}
}
}
public protocol ConstraintArrayConvertible {
func toArray() -> [NSLayoutConstraint]
}
extension NSLayoutConstraint: ConstraintArrayConvertible {
public func toArray() -> [NSLayoutConstraint] {
return [self]
}
}
extension Array: ConstraintArrayConvertible where Element == NSLayoutConstraint {
public func toArray() -> [NSLayoutConstraint] {
return self
}
}
public func activate(_ array: [ConstraintArrayConvertible]) {
let converted = array.flatMap { $0.toArray() }
NSLayoutConstraint.activate(converted)
}
public func activate(_ array: ConstraintArrayConvertible ...) {
let converted = array.flatMap { $0.toArray() }
NSLayoutConstraint.activate(converted)
}
let container = UIView()
let firstView = UIView()
container.addSubview(firstView)
let secondView = UIView()
container.addSubview(secondView)
activate(
firstView.centerYAnchor == secondView.centerYAnchor + 14 ~ .defaultLow,
[firstView.heightAnchor, secondView.widthAnchor] == 24 ~ 500,
firstView.leadingAnchor >= secondView.trailingAnchor ~ .defaultLow,
firstView.widthAnchor == secondView.widthAnchor * 1.2 + 12
)
// is equivalent to
let centerY = firstView.centerYAnchor.constraint(equalTo: secondView.centerYAnchor, constant: 14)
centerY.priority = .defaultLow
let height = firstView.heightAnchor.constraint(equalToConstant: 24)
height.priority = UILayoutPriority(500)
let width = secondView.widthAnchor.constraint(equalToConstant: 24)
width.priority = UILayoutPriority(500)
let leading = firstView.leadingAnchor.constraint(equalTo: secondView.trailingAnchor)
leading.priority = .defaultLow
NSLayoutConstraint.activate([
centerY,
height,
width,
leading,
firstView.widthAnchor.constraint(equalTo: secondView.widthAnchor, multiplier: 1.2, constant: 12)
])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment