-
-
Save mayoff/2abcd1c92ea7d3d99178fb70090fd4b9 to your computer and use it in GitHub Desktop.
Autolayout constraint literals in Swift (updated for Swift 2.2)
This file contains hidden or 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
#if os(iOS) | |
import UIKit | |
#else | |
import AppKit | |
#endif | |
let view = UIView(frame: CGRectMake(0, 0, 400, 300)) | |
view.backgroundColor = UIColor.redColor() | |
let button = UIButton(type: .Custom) | |
button.setTitle("Hello", forState: .Normal) | |
button.translatesAutoresizingMaskIntoConstraints = false | |
view.addSubview(button) | |
/// 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 options: NSLayoutFormatOptions = []) -> [NSLayoutConstraint] { | |
switch self { | |
case .UnevaluatedResult(let thunk): | |
return thunk(options) | |
default: | |
assertionFailure("result should never be called on an individual segment") | |
fatalError() | |
} | |
} | |
/// 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 segments.enumerate() { | |
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) | |
}) | |
} | |
// 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 = []) { | |
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 = []) { | |
activateConstraints(collection.result(options: options)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment