Skip to content

Instantly share code, notes, and snippets.

@erica
Created July 17, 2016 18:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erica/9c79f7e83ac1b3c139298a6c190e2f0d to your computer and use it in GitHub Desktop.
Save erica/9c79f7e83ac1b3c139298a6c190e2f0d to your computer and use it in GitHub Desktop.
/*
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