Skip to content

Instantly share code, notes, and snippets.

@mayoff
Last active January 4, 2016 19:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mayoff/c17bccb3d455b724ec40 to your computer and use it in GitHub Desktop.
Save mayoff/c17bccb3d455b724ec40 to your computer and use it in GitHub Desktop.
#if os(iOS)
import UIKit
typealias NSLayoutPriority = UILayoutPriority
#elseif os(OSX)
import AppKit
#endif
public extension NSLayoutAttribute {
/** Return a LayoutAnchor representing attribute `self` of `item`. */
public func of(item: AnyObject) -> LayoutAnchor {
return LayoutAnchor(item: item, attribute: self)
}
}
/**
I represent an attribute of an item that can participate in auto layout. For example, I can be the X-center of a button, or the baseline of a label, or the trailing edge of an image view.
You can use me to create constraints, like this:
typealias Layout = NSLayoutAttribute
Layout.Top.of(button) == Layout.Top.of(button.superview) + 20
The value of that expression is an `NSLayoutConstraint`. You might want to active it, change its priority, and save it in a property.
You can change a constraint's priority using `%=`, like this:
Layout.Top.of(button) == Layout.Top.of(button.superview) + 20 %= NSLayoutPriorityDefaultLow
Here's a fuller example:
NSLayoutConstraint.activateConstraints([
Layout.Top.of(button) == Layout.Top.of(button.superview) + 20 %= NSLayoutPriorityDefaultLow,
Layout.Bottom.of(superview) == Layout.Bottom.of(button) + 20,
Layout.Left.of(button) == Layout.Left.of(button.superview) + 20,
Layout.Right.of(button) <= Layout.Left.of(label) - 10
Layout.Right.of(label) == Layout.Right.of(label.superview) - 20,
Layout.Baseline.of(button) == Layout.Baseline.of(label),
])
*/
public struct LayoutAnchor {
let item: AnyObject?
let attribute: NSLayoutAttribute
let factor: CGFloat
let constant: CGFloat
private init(item: AnyObject?, attribute: NSLayoutAttribute, factor: CGFloat, constant: CGFloat) {
self.item = item
self.attribute = attribute
self.factor = factor
self.constant = constant
}
}
func *(anchor: LayoutAnchor, factor: CGFloat) -> LayoutAnchor {
assert(factor != 0, "\(anchor) can't be multiplied by zero.")
return LayoutAnchor(item: anchor.item, attribute: anchor.attribute, factor: factor * anchor.factor, constant: factor * anchor.constant)
}
func *(factor: CGFloat, anchor: LayoutAnchor) -> LayoutAnchor {
return anchor * factor
}
func /(anchor: LayoutAnchor, factor: CGFloat) -> LayoutAnchor {
return LayoutAnchor(item: anchor.item, attribute: anchor.attribute, factor: anchor.factor / factor, constant: anchor.constant / factor)
}
func +(anchor: LayoutAnchor, constant: CGFloat) -> LayoutAnchor {
return LayoutAnchor(item: anchor.item, attribute: anchor.attribute, factor: anchor.factor, constant: anchor.constant + constant)
}
func +(constant: CGFloat, anchor: LayoutAnchor) -> LayoutAnchor {
return LayoutAnchor(item: anchor.item, attribute: anchor.attribute, factor: anchor.factor, constant: anchor.constant + constant)
}
func -(anchor: LayoutAnchor, constant: CGFloat) -> LayoutAnchor {
return anchor + -constant
}
func -(constant: CGFloat, anchor: LayoutAnchor) -> LayoutAnchor {
return constant + -1 * anchor
}
public func ==(lhs: LayoutAnchor, rhs: LayoutAnchor) -> NSLayoutConstraint {
return constraintWithRelation(.Equal, from: lhs, to: rhs)
}
public func ==(lhs: CGFloat, rhs: LayoutAnchor) -> NSLayoutConstraint {
return constraintWithRelation(.Equal, from: lhs, to: rhs)
}
public func ==(lhs: LayoutAnchor, rhs: CGFloat) -> NSLayoutConstraint {
return constraintWithRelation(.Equal, from: lhs, to: rhs)
}
public func <=(lhs: LayoutAnchor, rhs: LayoutAnchor) -> NSLayoutConstraint {
return constraintWithRelation(.LessThanOrEqual, from: lhs, to: rhs)
}
public func <=(lhs: CGFloat, rhs: LayoutAnchor) -> NSLayoutConstraint {
return constraintWithRelation(.LessThanOrEqual, from: lhs, to: rhs)
}
public func <=(lhs: LayoutAnchor, rhs: CGFloat) -> NSLayoutConstraint {
return constraintWithRelation(.LessThanOrEqual, from: lhs, to: rhs)
}
public func >=(lhs: LayoutAnchor, rhs: LayoutAnchor) -> NSLayoutConstraint {
return constraintWithRelation(.GreaterThanOrEqual, from: lhs, to: rhs)
}
public func >=(lhs: CGFloat, rhs: LayoutAnchor) -> NSLayoutConstraint {
return constraintWithRelation(.GreaterThanOrEqual, from: lhs, to: rhs)
}
public func >=(lhs: LayoutAnchor, rhs: CGFloat) -> NSLayoutConstraint {
return constraintWithRelation(.GreaterThanOrEqual, from: lhs, to: rhs)
}
func %=(constraint: NSLayoutConstraint, priority: NSLayoutPriority) -> NSLayoutConstraint {
constraint.priority = priority
return constraint
}
func *=(identifier: String, constraint: NSLayoutConstraint) -> NSLayoutConstraint {
constraint.identifier = identifier
return constraint
}
////////////////////////////////////////////////////////////////////////
// MARK: - Implementation details
private extension LayoutAnchor {
private init(item: AnyObject, attribute: NSLayoutAttribute) {
self.init(item: item, attribute: attribute, factor: 1, constant: 0)
}
// Note: never let constant-only LayoutAnchors escape to the user. They must be immediately used to create constraints, which are then returned. If the user gets his hands on a constant-only LayoutAnchor, he can try to create a constraint between two constants, and that will crash.
private init(constant: CGFloat) {
self.init(item: nil, attribute: .NotAnAttribute, factor: 1, constant: constant)
}
}
private func constraintWithRelation(relation: NSLayoutRelation, from lhs: LayoutAnchor, to rhs: LayoutAnchor) -> NSLayoutConstraint {
if lhs.item == nil {
assert(rhs.item != nil, "I don't know how you managed it, but you've asked for a constraint between two constants \(lhs) and \(rhs).")
return constraintWithRelation(relation.opposite, from: rhs, to: lhs)
}
let constant = (rhs.constant - lhs.constant) / lhs.factor
let factor = rhs.factor / lhs.factor
let relation = lhs.factor < 0 ? relation.opposite : relation
return NSLayoutConstraint(item: lhs.item!, attribute: lhs.attribute, relatedBy: relation, toItem: rhs.item, attribute: rhs.attribute, multiplier: factor, constant: constant)
}
private func constraintWithRelation(relation: NSLayoutRelation, from lhs: LayoutAnchor, to rhs: CGFloat) -> NSLayoutConstraint {
return constraintWithRelation(relation, from: lhs, to: LayoutAnchor(constant: rhs))
}
private func constraintWithRelation(relation: NSLayoutRelation, from lhs: CGFloat, to rhs: LayoutAnchor) -> NSLayoutConstraint {
return constraintWithRelation(relation, from: LayoutAnchor(constant: lhs), to: rhs)
}
private extension NSLayoutRelation {
private var opposite: NSLayoutRelation {
switch self {
case .Equal: return self
case .LessThanOrEqual: return .GreaterThanOrEqual
case .GreaterThanOrEqual: return .LessThanOrEqual
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment