Created
July 17, 2016 18:38
-
-
Save erica/9c79f7e83ac1b3c139298a6c190e2f0d to your computer and use it in GitHub Desktop.
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
/* | |
ericasadun.com | |
Super-basic layout utilities | |
*/ | |
#if os(OSX) | |
import Cocoa | |
public typealias View = NSView | |
public typealias ViewController = NSViewController | |
public typealias LayoutPriority = NSLayoutPriority | |
#else | |
import UIKit | |
public typealias View = UIView | |
public typealias ViewController = UIViewController | |
public typealias LayoutPriority = UILayoutPriority | |
#endif | |
public let DefaultLayoutOptions: NSLayoutFormatOptions = [] | |
public let SkipConstraint = CGRect.null.origin.x | |
public extension NSLayoutConstraint { | |
/// Activates and prioritizes in one step | |
/// - parameter priority: Layout priority for the constraint | |
public func activate(priority: LayoutPriority) { | |
self.priority = priority | |
self.isActive = true | |
} | |
/// The constraint's first item cast to View type. | |
/// Should never be non-nil. | |
public var firstView: View { | |
guard let first = firstItem as? View else { return View() } | |
return first | |
} | |
/// The constraint's second item cast to View type. | |
/// May be nil. | |
public var secondView: View? { | |
return secondItem as? View | |
} | |
/// Expresses whether the constraint refers to a given view | |
public func refers(toView theView: View) -> Bool { | |
if firstView == theView { return true } | |
if let secondView = secondView { return secondView == theView } | |
return false | |
} | |
} | |
public extension View { | |
/// Adds multiple subviews at once | |
public func addSubviews(_ views: View...) { | |
views.forEach { | |
addSubview($0) | |
$0.translatesAutoresizingMaskIntoConstraints = false | |
} | |
} | |
/// Returns a view's superviews | |
public var superviews: [View] { | |
guard let superview = superview else { return [] } | |
return Array(sequence(first: superview, next: { $0.superview })) | |
} | |
} | |
#if !arch(arm64) | |
// toOpaque seems to be missing on Swift Playgrounds right now | |
extension View { | |
/// Overrides default description with view frame | |
public override var description: String { | |
return "[<\(self.dynamicType): \(Unmanaged.passUnretained(self).toOpaque())> \(self.frame)]" | |
} | |
} | |
#endif | |
public extension View { | |
/// Returns a list of external constraints that reference this view | |
public var externalConstraintReferences: [NSLayoutConstraint] { | |
var constraints: [NSLayoutConstraint] = [] | |
for superview in superviews { | |
for constraint in superview.constraints { | |
if constraint.refers(toView: self) { | |
constraints.append(constraint) | |
} | |
} | |
} | |
return constraints | |
} | |
/// Returns a list of internal constraints that reference this view | |
public var internalConstraintReferences: [NSLayoutConstraint] { | |
var constraints: [NSLayoutConstraint] = [] | |
for constraint in constraints { | |
if constraint.refers(toView: self) { | |
constraints.append(constraint) | |
} | |
} | |
return constraints | |
} | |
} | |
public extension View { | |
/// Provides more approachable auto layout control | |
public var autoLayoutEnabled: Bool { | |
get {return !translatesAutoresizingMaskIntoConstraints} | |
set {translatesAutoresizingMaskIntoConstraints = !newValue} | |
} | |
} | |
/// Constrain a group of views | |
/// - parameter priority: layout priority between 1 and 1000 | |
/// - parameter format: visual layout format string | |
/// - parameter views: in order from view1 to viewN | |
public func constrainViews( | |
priority: LayoutPriority = 1000, | |
_ format: String, views: View...) | |
{ | |
guard !views.isEmpty else { return } | |
var bindings: [String: View] = ["view": views.first!] | |
for (count, view) in views.enumerated() { | |
bindings["view"+String(count + 1)] = view | |
view.translatesAutoresizingMaskIntoConstraints = false | |
} | |
let constraints = NSLayoutConstraint.constraints( | |
withVisualFormat: format, options: [], | |
metrics: nil, views: bindings) | |
constraints.forEach { $0.activate(priority: priority) } | |
NSLayoutConstraint.activate(constraints) | |
} | |
/// Constrain a single view | |
public func constrainView( | |
priority: LayoutPriority = 1000, | |
_ format: String, view: View) { | |
constrainViews(priority: priority, format, views: view) | |
} | |
#if !os(OSX) | |
/// Stretch view to the edges of the parent view controller | |
public func StretchViewToViewController( | |
priority: LayoutPriority = 1000, | |
viewController: ViewController, | |
view: View, | |
insets: CGSize = .zero) | |
{ | |
view.translatesAutoresizingMaskIntoConstraints = false | |
if view.superview == nil { viewController.view.addSubview(view) } | |
guard let superview = viewController.view else { return } | |
view.topAnchor.constraint( | |
equalTo: superview.topAnchor, constant: insets.height) | |
.activate(priority: priority) | |
view.bottomAnchor.constraint( | |
equalTo: superview.bottomAnchor, constant: insets.height) | |
.activate(priority: priority) | |
view.leadingAnchor.constraint( | |
equalTo: superview.leadingAnchor, constant: insets.width) | |
.activate(priority: priority) | |
view.trailingAnchor.constraint( | |
equalTo: superview.trailingAnchor, constant: insets.width) | |
.activate(priority: priority) | |
} | |
#endif | |
extension View { | |
/// Stretch view to superview | |
/// - parameter h: should stretch horizontally | |
/// - parameter v: should stretch vertically | |
public func stretchToSuperview( | |
priority: LayoutPriority = 1000, | |
h: Bool = true, v: Bool = true) | |
{ | |
#if os(OSX) | |
guard let _ = superview else { self.print("no superview") ; return } | |
#else | |
guard let _ = superview else { print("no superview") ; return } | |
#endif | |
translatesAutoresizingMaskIntoConstraints = false | |
if h { constrainView(priority: priority, "H:|[view]|", view: self) } | |
if v { constrainView(priority: priority, "V:|[view]|", view: self) } | |
} | |
/// Center view in superview | |
/// - parameter h: should center horizontally | |
/// - parameter v: should center vertically | |
public func centerInSuperview( | |
priority: LayoutPriority = 1000, | |
h: Bool = true, v: Bool = true) { | |
#if os(OSX) | |
guard let superview = superview else { self.print("no superview") ; return } | |
#else | |
guard let superview = superview else { print("no superview") ; return } | |
#endif | |
translatesAutoresizingMaskIntoConstraints = false | |
if h { centerXAnchor.constraint( | |
equalTo: superview.centerXAnchor) | |
.activate(priority: priority) | |
} | |
if v { centerYAnchor.constraint( | |
equalTo: superview.centerYAnchor) | |
.activate(priority: priority) | |
} | |
} | |
/// Set size constraint | |
/// - Note: Set size's width or height to SkipConstraint to skip | |
public func constrainSize( | |
priority: LayoutPriority = 1000, | |
size: CGSize) | |
{ | |
if size.width != SkipConstraint { | |
widthAnchor.constraint(equalToConstant: size.width) | |
.activate(priority: priority) | |
} | |
if size.height != SkipConstraint { | |
heightAnchor.constraint(equalToConstant: size.height) | |
.activate(priority: priority) | |
} | |
} | |
/// Set minimum size constraint | |
/// - Note: Set size's width or height to SkipConstraint to skip | |
public func constrainMinimumSize( | |
priority: LayoutPriority = 1000, | |
size: CGSize | |
) | |
{ | |
if size.width != SkipConstraint { | |
widthAnchor.constraint(greaterThanOrEqualToConstant: size.width) | |
.activate(priority: priority) | |
} | |
if size.height != SkipConstraint { | |
heightAnchor.constraint(greaterThanOrEqualToConstant: size.height) | |
.activate(priority: priority) | |
} | |
} | |
/// Set maximum size constraint | |
/// - Note: Set size's width or height to SkipConstraint to skip | |
public func constrainMaximumSize( | |
priority: LayoutPriority = 1000, | |
size: CGSize) | |
{ | |
if size.width != SkipConstraint { | |
widthAnchor.constraint(lessThanOrEqualToConstant: size.width) | |
.activate(priority: priority) | |
} | |
if size.height != SkipConstraint { | |
heightAnchor.constraint(lessThanOrEqualToConstant: size.height) | |
.activate(priority: priority) | |
} | |
} | |
/// Set position | |
/// - Note: Set location's x or y to SkipConstraint to skip | |
public func constrainPosition( | |
priority: LayoutPriority = 1000, | |
position: CGPoint) | |
{ | |
#if os(OSX) | |
guard let superview = superview else { self.print("no superview") ; return } | |
#else | |
guard let superview = superview else { print("no superview") ; return } | |
#endif | |
if position.x != SkipConstraint { | |
leftAnchor.constraint( | |
greaterThanOrEqualTo: superview.leftAnchor, constant: position.x) | |
.activate(priority: priority) | |
} | |
if position.y != SkipConstraint { | |
topAnchor.constraint( | |
greaterThanOrEqualTo: superview.topAnchor, constant: position.y) | |
.activate(priority: priority) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment