Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Autolayout constraint literals in Swift
#if os(iOS)
import UIKit
#else
import AppKit
#endif
/// A set of constraints prepared from a visual format string, in the style of
/// `NSLayoutConstraint.constraintsWithVisualFormat()`, with the additional ability
/// to supply views and metrics in a string interpolation.
///
/// This type is meant to be used implicitly via the convenience functions defined below; for example,
///
/// view.addConstraints("|-[\(button)]-(>=\(kSeparation)@750)-[\(slider)(==\(button))]-|", .AlignAllCenterY)
///
enum ConstraintCollection: StringInterpolationConvertible {
#if os(iOS)
typealias NativeViewType = UIView
#else
typealias NativeViewType = NSView
#endif
case FormatSegment(String)
case ViewSegment(NativeViewType)
case MetricSegment(CGFloat)
case UnevaluatedResult(NSLayoutFormatOptions -> [NSLayoutConstraint])
/// Evaluates the format, with the given `options`, to produce actual constraints.
func result(options: NSLayoutFormatOptions = nil) -> [NSLayoutConstraint] {
switch self {
case .UnevaluatedResult(let thunk):
return thunk(options)
default:
assertionFailure("result should never be called on an individual segment")
}
}
/// Convenience initializer for ConstraintCollection("format string") syntax.
init(_ collection: ConstraintCollection) { self = collection }
/// Prepares a collection of constraints with the views and metrics specified.
init(stringInterpolation segments: ConstraintCollection...) {
var format = ""
var views = [:] as [String: NativeViewType]
var metrics = [:] as [String: NSNumber]
// From the interpolation segments, build a format string and view/metrics dictionaries.
for (i, segment) in enumerate(segments) {
switch segment {
case .FormatSegment(let str):
format += str
case .ViewSegment(let view):
let key = "v\(i)"
format += key
views[key] = view
case .MetricSegment(let metric):
let key = "m\(i)"
format += key
metrics[key] = metric
case .UnevaluatedResult:
assertionFailure("interpolation result passed to final initializer; this shouldn't happen")
}
}
self = .UnevaluatedResult({
NSLayoutConstraint.constraintsWithVisualFormat(
format, options: $0, metrics: metrics, views: views) as! [NSLayoutConstraint]
})
}
// Initializers for valid interpolation segments.
init(stringInterpolationSegment expr: String) { self = .FormatSegment(expr) }
init(stringInterpolationSegment expr: NativeViewType) { self = .ViewSegment(expr) }
init(stringInterpolationSegment expr: ConstraintMetricType) { self = .MetricSegment(expr.metric) }
// Generic initializer required for StringInterpolationConvertible.
init<T>(stringInterpolationSegment expr: T) {
fatalError("\(expr.dynamicType) is not valid in a constraint collection literal")
}
}
// Wish we didn't have to do this...
protocol ConstraintMetricType { var metric: CGFloat { get } }
extension Float: ConstraintMetricType { var metric: CGFloat { return CGFloat(self) } }
extension Double: ConstraintMetricType { var metric: CGFloat { return CGFloat(self) } }
extension UInt8: ConstraintMetricType { var metric: CGFloat { return CGFloat(self) } }
extension Int8: ConstraintMetricType { var metric: CGFloat { return CGFloat(self) } }
extension UInt16: ConstraintMetricType { var metric: CGFloat { return CGFloat(self) } }
extension Int16: ConstraintMetricType { var metric: CGFloat { return CGFloat(self) } }
extension UInt32: ConstraintMetricType { var metric: CGFloat { return CGFloat(self) } }
extension Int32: ConstraintMetricType { var metric: CGFloat { return CGFloat(self) } }
extension UInt64: ConstraintMetricType { var metric: CGFloat { return CGFloat(self) } }
extension Int64: ConstraintMetricType { var metric: CGFloat { return CGFloat(self) } }
extension UInt: ConstraintMetricType { var metric: CGFloat { return CGFloat(self) } }
extension Int: ConstraintMetricType { var metric: CGFloat { return CGFloat(self) } }
extension ConstraintCollection.NativeViewType {
/// Adds a collection of constraints specified as a string interpolation literal.
func addConstraints(collection: ConstraintCollection, _ options: NSLayoutFormatOptions = nil) {
addConstraints(collection.result(options: options))
}
}
extension NSLayoutConstraint {
/// Activates a collection of constraints specified as a string interpolation literal.
class func activateConstraints(collection: ConstraintCollection, _ options: NSLayoutFormatOptions = nil) {
activateConstraints(collection.result(options: options))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.