Last active
November 28, 2019 18:50
-
-
Save SzymonMrozek/ee5c929b0410db95103e806cac108e66 to your computer and use it in GitHub Desktop.
Set of useful operators for more readable creation of constraints
This file contains 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
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