Created
September 9, 2016 17:33
-
-
Save mergesort/7699be4646cff7c1e1b8ebe6862db988 to your computer and use it in GitHub Desktop.
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 | |
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