Skip to content

Instantly share code, notes, and snippets.

@mergesort
Created September 9, 2016 17:33
Show Gist options
  • Save mergesort/7699be4646cff7c1e1b8ebe6862db988 to your computer and use it in GitHub Desktop.
Save mergesort/7699be4646cff7c1e1b8ebe6862db988 to your computer and use it in GitHub Desktop.
import UIKit
public protocol Anchor {
var rawValue: Int { get }
var constant: CGFloat { get }
var priority: UILayoutPriority { get }
init(rawValue: Int, constant: CGFloat, priority: UILayoutPriority)
}
public extension Anchor {
public init(rawValue: Int) {
self.init(rawValue: rawValue, constant: 0.0, priority: UILayoutPriorityRequired)
}
}
public struct HorizontalEdgeAnchor: OptionSet, Anchor {
public let rawValue: Int
public let constant: CGFloat
public let priority: UILayoutPriority
public init(rawValue: Int, constant: CGFloat, priority: UILayoutPriority = UILayoutPriorityRequired) {
self.rawValue = rawValue
self.constant = constant
self.priority = priority
}
public static let leading = HorizontalEdgeAnchor(rawValue: 1 << 1)
public static let trailing = HorizontalEdgeAnchor(rawValue: 1 << 2)
public static let centerX = HorizontalEdgeAnchor(rawValue: 1 << 3)
public static let allEdges: [HorizontalEdgeAnchor] = [ .leading, .trailing ]
public static func leading(constant: CGFloat, priority: UILayoutPriority = UILayoutPriorityRequired) -> HorizontalEdgeAnchor {
return HorizontalEdgeAnchor(rawValue: HorizontalEdgeAnchor.leading.rawValue, constant: constant, priority: priority)
}
public static func trailing(constant: CGFloat, priority: UILayoutPriority = UILayoutPriorityRequired) -> HorizontalEdgeAnchor {
return HorizontalEdgeAnchor(rawValue: HorizontalEdgeAnchor.trailing.rawValue, constant: constant, priority: priority)
}
public static func centerX(constant: CGFloat, priority: UILayoutPriority = UILayoutPriorityRequired) -> HorizontalEdgeAnchor {
return HorizontalEdgeAnchor(rawValue: HorizontalEdgeAnchor.centerX.rawValue, constant: constant, priority: priority)
}
fileprivate func anchor(forView view: UIView) -> NSLayoutXAxisAnchor {
switch self {
case HorizontalEdgeAnchor.leading:
return view.leadingAnchor
case HorizontalEdgeAnchor.trailing:
return view.trailingAnchor
case HorizontalEdgeAnchor.centerX:
return view.centerXAnchor
}
}
}
public struct VerticalEdgeAnchor: OptionSet, Anchor {
public var rawValue: Int
public var constant: CGFloat
public var priority: UILayoutPriority
public init(rawValue: Int, constant: CGFloat, priority: UILayoutPriority = UILayoutPriorityRequired) {
self.rawValue = rawValue
self.constant = constant
self.priority = priority
}
public static let top = VerticalEdgeAnchor(rawValue: 1 << 1)
public static let bottom = VerticalEdgeAnchor(rawValue: 1 << 2)
public static let centerY = VerticalEdgeAnchor(rawValue: 1 << 3)
public static let allEdges: [VerticalEdgeAnchor] = [ .top, .bottom ]
public static func top(constant: CGFloat, priority: UILayoutPriority = UILayoutPriorityRequired) -> VerticalEdgeAnchor {
return VerticalEdgeAnchor(rawValue: VerticalEdgeAnchor.top.rawValue, constant: constant, priority: priority)
}
public static func bottom(constant: CGFloat, priority: UILayoutPriority = UILayoutPriorityRequired) -> VerticalEdgeAnchor {
return VerticalEdgeAnchor(rawValue: VerticalEdgeAnchor.bottom.rawValue, constant: constant, priority: priority)
}
public static func centerY(constant: CGFloat, priority: UILayoutPriority = UILayoutPriorityRequired) -> VerticalEdgeAnchor {
return VerticalEdgeAnchor(rawValue: VerticalEdgeAnchor.centerY.rawValue, constant: constant, priority: priority)
}
fileprivate func anchor(forView view: UIView) -> NSLayoutYAxisAnchor {
switch self {
case VerticalEdgeAnchor.top:
return view.topAnchor
case VerticalEdgeAnchor.bottom:
return view.bottomAnchor
case VerticalEdgeAnchor.centerY:
return view.centerYAnchor
}
}
}
public struct SizeAnchor: OptionSet, Anchor {
public var rawValue: Int
public var constant: CGFloat
public var priority: UILayoutPriority
public init(rawValue: Int, constant: CGFloat, priority: UILayoutPriority = UILayoutPriorityRequired) {
self.rawValue = rawValue
self.constant = constant
self.priority = priority
}
public static let width = SizeAnchor(rawValue: 1 << 1)
public static let height = SizeAnchor(rawValue: 1 << 2)
public static func width(constant: CGFloat, priority: UILayoutPriority = UILayoutPriorityRequired) -> SizeAnchor {
return SizeAnchor(rawValue: SizeAnchor.width.rawValue, constant: constant, priority: priority)
}
public static func height(constant: CGFloat, priority: UILayoutPriority = UILayoutPriorityRequired) -> SizeAnchor {
return SizeAnchor(rawValue: SizeAnchor.height.rawValue, constant: constant, priority: priority)
}
fileprivate func dimension(forView view: UIView) -> NSLayoutDimension {
switch self {
case SizeAnchor.width:
return view.widthAnchor
case SizeAnchor.height:
return view.heightAnchor
}
}
}
public extension UIView {
static func setTranslateAutoresizingMasks(views: [UIView], on: Bool) {
views.forEach { $0.translatesAutoresizingMaskIntoConstraints = on }
}
}
public extension UIView {
func pinToSuperview(relation: NSLayoutRelation = .equal, activate: Bool = true) -> [NSLayoutConstraint] {
if let superview = self.superview {
let horizontalConstraints = self.pinToView(superview, edges: HorizontalEdgeAnchor.allEdges, activate: activate)
let verticalConstraints = self.pinToView(superview, edges: VerticalEdgeAnchor.allEdges, activate: activate)
let allConstraints = [ horizontalConstraints, verticalConstraints ].flatMap { $0 }
return allConstraints
} else {
fatalError("Cannot pin to a nil superview")
}
}
func pinToSuperview(edges: [HorizontalEdgeAnchor] = HorizontalEdgeAnchor.allEdges, relation: NSLayoutRelation = .equal, activate: Bool = true) -> [NSLayoutConstraint] {
if let superview = self.superview {
return self.pinToView(superview, edges: edges, activate: activate)
} else {
fatalError("Cannot pin to a nil superview")
}
}
func pinToSuperview(edges: [VerticalEdgeAnchor] = VerticalEdgeAnchor.allEdges, relation: NSLayoutRelation = .equal, activate: Bool = true) -> [NSLayoutConstraint] {
if let superview = self.superview {
return self.pinToView(superview, edges: edges, activate: activate)
} else {
fatalError("Cannot pin to a nil superview")
}
}
func pinToView(_ view: UIView, edges: [HorizontalEdgeAnchor], relation: NSLayoutRelation = .equal, activate: Bool = true) -> [NSLayoutConstraint] {
let addConstraint: (_ edge: HorizontalEdgeAnchor) -> NSLayoutConstraint? = { edge in
if edges.contains(edge) {
let constant: CGFloat
let priority: UILayoutPriority
if let index = edges.index(of: edge) {
let currentEdge = edges[index]
constant = currentEdge.constant
priority = currentEdge.priority
} else {
constant = 0.0
priority = UILayoutPriorityRequired
}
let currentAnchor = edge.anchor(forView: self)
let viewAnchor = edge.anchor(forView: view)
let constraint: NSLayoutConstraint
if relation == .greaterThanOrEqual {
constraint = currentAnchor.constraint(greaterThanOrEqualTo: viewAnchor, constant: constant)
} else if relation == .lessThanOrEqual {
constraint = currentAnchor.constraint(lessThanOrEqualTo: viewAnchor, constant: constant)
} else {
constraint = currentAnchor.constraint(equalTo: viewAnchor, constant: constant)
}
constraint.priority = priority
return constraint
}
return nil
}
let leadingConstraint = addConstraint(.leading)
let trailingConstraint = addConstraint(.trailing)
let centerXConstraint = addConstraint(.centerX)
// let topConstraint = addConstraint(.top)
// let bottomConstraint = addConstraint(.bottom)
// let centerYConstraint = addConstraint(.centerY)
// let viewConstraints = [ topConstraint, bottomConstraint, centerYConstraint ].flatMap { $0 }
// let widthConstraint = addConstraint(.width)
// let heightConstraint = addConstraint(.height)
// let viewConstraints = [ widthConstraint, heightConstraint ].flatMap { $0 }
self.translatesAutoresizingMaskIntoConstraints = false
let viewConstraints = [ leadingConstraint, trailingConstraint, centerXConstraint ].flatMap { $0 }
viewConstraints.setActive(activate)
return viewConstraints
}
// pinToView needs to be across all 3 generics
func pinToView(_ view: UIView, edges: [VerticalEdgeAnchor], relation: NSLayoutRelation = .equal, activate: Bool = true) -> [NSLayoutConstraint] {
return []
}
// pin edge needs to be across all 3 generics
func pinEdge(_ edge: Anchor, toEdge: Anchor, ofView: UIView, relation: NSLayoutRelation = .equal, constant: CGFloat = 0.0, priority: UILayoutPriority = UILayoutPriorityRequired, activate: Bool = true) -> NSLayoutConstraint {
let fromAnchor = self.layoutAnchorForEdge(edge)
let toAnchor = ofView.layoutAnchorForEdge(toEdge)
let constraint: NSLayoutConstraint
if relation == .greaterThanOrEqual {
constraint = fromAnchor.constraintGreaterThanOrEqualToAnchor(toAnchor, constant: constant)
} else if relation == .lessThanOrEqual {
constraint = fromAnchor.constraintLessThanOrEqualToAnchor(toAnchor, constant: constant)
} else {
constraint = fromAnchor.constraintEqualToAnchor(toAnchor, constant: constant)
}
constraint.priority = priority
constraint.isActive = activate
return constraint
}
func setSize(_ sizeAnchor: SizeAnchor, relation: NSLayoutRelation = .equal, activate: Bool = true) -> NSLayoutConstraint {
self.translatesAutoresizingMaskIntoConstraints = false
let currentDimension = sizeAnchor.dimension(forView: self)
let constraint: NSLayoutConstraint
if relation == .greaterThanOrEqual {
constraint = currentDimension.constraint(greaterThanOrEqualToConstant: sizeAnchor.constant)
} else if relation == .lessThanOrEqual {
constraint = currentDimension.constraint(lessThanOrEqualToConstant: sizeAnchor.constant)
} else {
constraint = currentDimension.constraint(equalToConstant: sizeAnchor.constant)
}
constraint.priority = sizeAnchor.priority
constraint.isActive = activate
return constraint
}
func setSize(sizeAnchors: [SizeAnchor] = [ SizeAnchor.width, SizeAnchor.height ], relation: NSLayoutRelation = .equal, activate: Bool = true) -> [NSLayoutConstraint] {
return sizeAnchors.map { return self.setSize($0, relation: relation, activate: activate) }
}
func setRelativeSize(sizeAnchor: SizeAnchor, toSizeAnchor: SizeAnchor, ofView: UIView, multiplier: CGFloat, constant: CGFloat, relation: NSLayoutRelation = .equal, activate: Bool = true) -> NSLayoutConstraint {
self.translatesAutoresizingMaskIntoConstraints = false
let fromDimension = sizeAnchor.dimension(forView: self)
let toDimension = sizeAnchor.dimension(forView: ofView)
let constraint: NSLayoutConstraint
if relation == .greaterThanOrEqual {
constraint = fromDimension.constraint(greaterThanOrEqualTo: toDimension, multiplier: multiplier, constant: constant)
} else if relation == .lessThanOrEqual {
constraint = fromDimension.constraint(lessThanOrEqualTo: toDimension, multiplier: multiplier, constant: constant)
} else {
constraint = fromDimension.constraint(equalTo: toDimension, multiplier: multiplier, constant: constant)
}
constraint.priority = sizeAnchor.priority
constraint.isActive = activate
return constraint
}
}
public extension NSLayoutConstraint {
static func activateAllConstraints(constraints: [[NSLayoutConstraint]]) {
NSLayoutConstraint.activate(constraints.flatMap { $0 })
}
static func deactivateAllConstraints(constraints: [[NSLayoutConstraint]]) {
NSLayoutConstraint.deactivate(constraints.flatMap { $0 })
}
}
// MARK: Objective-C API
public extension UIView {
@available(*, unavailable, message: "Only to be used from Objective-C") func objc_pinToView(view: UIView, inset: UIEdgeInsets = .zero) -> [NSLayoutConstraint] {
return self._objcPinToView(view)
}
@available(*, unavailable, message: "Only to be used from Objective-C") func objc_pinToSuperview(inset: UIEdgeInsets = .zero) -> [NSLayoutConstraint] {
if let superview = self.superview {
return self._objcPinToView(superview, inset: inset)
} else {
fatalError("Cannot pin to a nil superview")
}
}
}
private extension UIView {
func _objcPinToView(_ view: UIView, inset: UIEdgeInsets = UIEdgeInsets.zero) -> [NSLayoutConstraint] {
let viewConstraints: [NSLayoutConstraint] = [
self.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: inset.left),
self.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: inset.right),
self.topAnchor.constraint(equalTo: view.topAnchor, constant: inset.top),
self.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: inset.bottom),
]
return viewConstraints
}
}
private extension Array where Element: NSLayoutConstraint {
func setActive(_ active: Bool) {
if active {
NSLayoutConstraint.activate(self)
} else {
NSLayoutConstraint.deactivate(self)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment