Last active
February 19, 2020 01:44
-
-
Save EmperiorEric/bf4d76c550e338c31c31dea076dee7f1 to your computer and use it in GitHub Desktop.
A simple extension I wrote that makes working with visual format for AutoLayout much more enjoyable.
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
// | |
// NSLayoutConstraint+StandardMetrics.swift | |
// | |
// Created by Ryan Poolos on 4/22/15. | |
// Copyright (c) 2015 Frozen Fire Studios, Inc. All rights reserved. | |
// | |
import UIKit | |
extension Double { | |
public static var halfPadding = 4.0 | |
public static var padding = 8.0 | |
public static var thickPadding = 12.0 | |
public static var margin = 16.0 | |
public static var thickMargin = 24.0 | |
public static var doubleMargin = 32.0 | |
public static var pixelHeight = 1.0 / Double(UIScreen.main.scale) | |
} | |
extension CGFloat { | |
public static var halfPadding: CGFloat = 4.0 | |
public static var padding: CGFloat = 8.0 | |
public static var thickPadding: CGFloat = 12.0 | |
public static var margin: CGFloat = 16.0 | |
public static var thickMargin: CGFloat = 24.0 | |
public static var doubleMargin: CGFloat = 32.0 | |
public static var pixelHeight: CGFloat = 1.0 / UIScreen.main.scale | |
} | |
public let halfPaddingMetricKey = "halfPadding" | |
public let paddingMetricKey = "padding" | |
public let thickPaddingMetricKey = "thickPadding" | |
public let marginMetricKey = "margin" | |
public let thickMarginMetricKey = "thickMargin" | |
public let doubleMarginMetricKey = "doubleMargin" | |
public let pixelHeightMetricKey = "pixel" | |
private var _standardMetrics: [String: Double] = [ | |
halfPaddingMetricKey: .halfPadding, | |
paddingMetricKey: .padding, | |
thickPaddingMetricKey: .thickPadding, | |
marginMetricKey: .margin, | |
thickMarginMetricKey: .thickMargin, | |
doubleMarginMetricKey: .doubleMargin, | |
pixelHeightMetricKey: .pixelHeight, | |
] | |
public extension NSLayoutConstraint { | |
//========================================================================== | |
// MARK: - Standard Metrics | |
//========================================================================== | |
static var standardMetrics: [String: Double] { | |
return _standardMetrics | |
} | |
class func addStandardMetric(_ key: String, value: Double) { | |
_standardMetrics[key] = value | |
} | |
//========================================================================== | |
// MARK: - Visual Format Convenience | |
//========================================================================== | |
class func constraintsWithFormat(_ format: String, views: [String: Any], metrics: [String: Double] = standardMetrics, options: NSLayoutConstraint.FormatOptions = []) -> [NSLayoutConstraint] { | |
return constraints(withVisualFormat: format, options: options, metrics: metrics, views: views) | |
} | |
@discardableResult class func activeConstraintsWithFormat(_ format: String, views: [String: Any], metrics: [String: Double] = standardMetrics, options: NSLayoutConstraint.FormatOptions = []) -> [NSLayoutConstraint] { | |
let constraints = constraintsWithFormat(format, views: views, metrics: metrics, options: options) | |
activate(constraints) | |
return constraints | |
} | |
//========================================================================== | |
// MARK: - Fill Superview | |
//========================================================================== | |
/// Returns inactive constraints for a given view to fill its superview | |
/// minus the given fixed insets. | |
class func constraintsToFillSuperview(view: UIView, insets: UIEdgeInsets) -> [NSLayoutConstraint] { | |
let views = ["view": view] | |
var constraints: [NSLayoutConstraint] = [] | |
constraints.append(contentsOf: constraintsWithFormat("H:|-(\(insets.left))-[view]-(\(insets.right))-|", views: views)) | |
constraints.append(contentsOf: constraintsWithFormat("V:|-(\(insets.top))-[view]-(\(insets.bottom))-|", views: views)) | |
return constraints | |
} | |
/// Returns inactive constraints for a given view to fill its superview | |
/// minus fixed horizontal and vertical margins. | |
class func constraintsToFillSuperview(view: UIView, horizontal: CGFloat = 0, vertical: CGFloat = 0) -> [NSLayoutConstraint] { | |
return constraintsToFillSuperview(view: view, insets: UIEdgeInsets(horizontal: horizontal, vertical: vertical)) | |
} | |
/// Returns inactive constraints for a given view to fill its superview | |
/// minus fixed equal margins. | |
class func constraintsToFillSuperview(view: UIView, margin: CGFloat = 0) -> [NSLayoutConstraint] { | |
return constraintsToFillSuperview(view: view, insets: UIEdgeInsets(all: margin)) | |
} | |
/// Returns already active constraints for a given view to fill its | |
/// superview minus the given fixed insets. | |
@discardableResult class func activeConstraintsToFillSuperview(view: UIView, insets: UIEdgeInsets) -> [NSLayoutConstraint] { | |
let constraints = constraintsToFillSuperview(view: view, insets: insets) | |
activate(constraints) | |
return constraints | |
} | |
/// Returns already active constraints for a given view to fill its | |
/// superview minus fixed horizontal and vertical margins. | |
@discardableResult class func activeConstraintsToFillSuperview(view: UIView, horizontal: CGFloat = 0, vertical: CGFloat = 0) -> [NSLayoutConstraint] { | |
let constraints = constraintsToFillSuperview(view: view, horizontal: horizontal, vertical: vertical) | |
activate(constraints) | |
return constraints | |
} | |
/// Returns already active constraints for a given view to fill its superview | |
/// minus fixed equal margins. | |
@discardableResult class func activeConstraintsToFillSuperview(view: UIView, margin: CGFloat = 0) -> [NSLayoutConstraint] { | |
let constraints = constraintsToFillSuperview(view: view, margin: margin) | |
activate(constraints) | |
return constraints | |
} | |
/// Sets the given view to fill its superview horizontally minus a fixed margin | |
class func constraintsToHorizontallyFillSuperview(view: UIView, margin: CGFloat = 0) -> [NSLayoutConstraint] { | |
let views = ["view": view] | |
return constraintsWithFormat("H:|-(\(margin))-[view]-(\(margin))-|", views: views) | |
} | |
/// Sets the given view to fill its superview horizontally minus a fixed margin | |
@discardableResult class func activeConstraintsToHorizontallyFillSuperview(view: UIView, margin: CGFloat = 0) -> [NSLayoutConstraint] { | |
let views = ["view": view] | |
return activeConstraintsWithFormat("H:|-(\(margin))-[view]-(\(margin))-|", views: views) | |
} | |
/// Sets the given view to fill its superview vertically minus a fixed margin | |
class func constraintsToVerticallyFillSuperview(view: UIView, margin: CGFloat = 0) -> [NSLayoutConstraint] { | |
let views = ["view": view] | |
return constraintsWithFormat("V:|-(\(margin))-[view]-(\(margin))-|", views: views) | |
} | |
/// Sets the given view to fill its superview vertically minus a fixed margin | |
@discardableResult class func activeConstraintsToVerticallyFillSuperview(view: UIView, margin: CGFloat = 0) -> [NSLayoutConstraint] { | |
let views = ["view": view] | |
return activeConstraintsWithFormat("V:|-(\(margin))-[view]-(\(margin))-|", views: views) | |
} | |
/// Sets the given view to fill its superview vertically minus a fixed margin | |
class func constraintsToVerticallyFillSuperviewSafeArea(view: UIView, margin: CGFloat = 0) -> [NSLayoutConstraint] { | |
guard let superview = view.superview else { | |
return [] | |
} | |
return [ | |
view.topAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.topAnchor, constant: margin), | |
view.bottomAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.bottomAnchor, constant: -margin), | |
] | |
} | |
/// Sets the given view to fill its superview vertically minus a fixed margin | |
@discardableResult class func activeConstraintsToVerticallyFillSuperviewSafeArea(view: UIView, margin: CGFloat = 0) -> [NSLayoutConstraint] { | |
let constraints = constraintsToVerticallyFillSuperviewSafeArea(view: view, margin: margin) | |
activate(constraints) | |
return constraints | |
} | |
//========================================================================== | |
// MARK: - Contain In Superview | |
//========================================================================== | |
/// Sets the given view to be contained within its superview plus a greater than or equal to the minimum margin given. | |
class func constraintsToContainInSuperview(view: UIView, minimumMargin: CGFloat = 0) -> [NSLayoutConstraint] { | |
let views = ["view": view] | |
var constraints: [NSLayoutConstraint] = [] | |
constraints.append(contentsOf: constraintsWithFormat("H:|-(>=\(minimumMargin))-[view]-(>=\(minimumMargin))-|", views: views)) | |
constraints.append(contentsOf: constraintsWithFormat("V:|-(>=\(minimumMargin))-[view]-(>=\(minimumMargin))-|", views: views)) | |
return constraints | |
} | |
/// Sets the given view to be contained within its superview plus a greater than or equal to the minimum margin given. | |
@discardableResult class func activeConstraintsToContainInSuperview(view: UIView, minimumMargin: CGFloat = 0) -> [NSLayoutConstraint] { | |
var constraints: [NSLayoutConstraint] = [] | |
constraints.append(contentsOf: constraintsToContainVerticallyInSuperview(view: view, minimumMargin: minimumMargin)) | |
constraints.append(contentsOf: constraintsToContainHorizontallyInSuperview(view: view, minimumMargin: minimumMargin)) | |
activate(constraints) | |
return constraints | |
} | |
/// Sets the given view to be contained horizontally within its superview plus a greater than or equal to the minimum margin given. | |
class func constraintsToContainHorizontallyInSuperview(view: UIView, minimumMargin: CGFloat = 0) -> [NSLayoutConstraint] { | |
let views = ["view": view] | |
return constraintsWithFormat("H:|-(>=\(minimumMargin))-[view]-(>=\(minimumMargin))-|", views: views) | |
} | |
/// Sets the given view to be contained horizontally within its superview plus a greater than or equal to the minimum margin given. | |
@discardableResult class func activeConstraintsToContainHorizontallyInSuperview(view: UIView, minimumMargin: CGFloat = 0) -> [NSLayoutConstraint] { | |
let constraints = constraintsToContainHorizontallyInSuperview(view: view, minimumMargin: minimumMargin) | |
activate(constraints) | |
return constraints | |
} | |
/// Sets the given view to be contained vertically within its superview plus a greater than or equal to the minimum margin given. | |
class func constraintsToContainVerticallyInSuperview(view: UIView, minimumMargin: CGFloat = 0) -> [NSLayoutConstraint] { | |
let views = ["view": view] | |
return constraintsWithFormat("V:|-(>=\(minimumMargin))-[view]-(>=\(minimumMargin))-|", views: views) | |
} | |
/// Sets the given view to be contained vertically within its superview plus a greater than or equal to the minimum margin given. | |
@discardableResult class func activeConstraintsToContainVerticallyInSuperview(view: UIView, minimumMargin: CGFloat = 0) -> [NSLayoutConstraint] { | |
let constraints = constraintsToContainVerticallyInSuperview(view: view, minimumMargin: minimumMargin) | |
activate(constraints) | |
return constraints | |
} | |
//========================================================================== | |
// MARK: - Center In Superview | |
//========================================================================== | |
/// Centers a given view within its superview with optional offset | |
class func constraintsToCenterInSuperview(view: UIView, offset: CGSize = .zero) -> [NSLayoutConstraint] { | |
guard let superview = view.superview else { | |
fatalError("View must have superview to center in superview: \(view)") | |
} | |
let constraints: [NSLayoutConstraint] = [ | |
view.centerXAnchor.constraint(equalTo: superview.centerXAnchor, constant: offset.width), | |
view.centerYAnchor.constraint(equalTo: superview.centerYAnchor, constant: offset.height), | |
] | |
return constraints | |
} | |
/// Centers a given view within its superview with optional offset | |
@discardableResult class func activeConstraintsToCenterInSuperview(view: UIView, offset: CGSize = .zero) -> [NSLayoutConstraint] { | |
let constraints = constraintsToCenterInSuperview(view: view, offset: offset) | |
activate(constraints) | |
return constraints | |
} | |
/// Centers a given view based on the center of another given view with optional offset. | |
class func constraintsToCenter(view: Anchorable, to otherView: Anchorable, offset: CGSize = .zero) -> [NSLayoutConstraint] { | |
let constraints: [NSLayoutConstraint] = [ | |
view.centerXAnchor.constraint(equalTo: otherView.centerXAnchor, constant: offset.width), | |
view.centerYAnchor.constraint(equalTo: otherView.centerYAnchor, constant: offset.height), | |
] | |
return constraints | |
} | |
/// Centers a given view based on the center of another given view with optional offset. | |
@discardableResult class func activeConstraintsToCenter(view: Anchorable, to otherView: Anchorable, offset: CGSize = .zero) -> [NSLayoutConstraint] { | |
let constraints = constraintsToCenter(view: view, to: otherView, offset: offset) | |
activate(constraints) | |
return constraints | |
} | |
//========================================================================== | |
// MARK: - Pin Edges to Other View | |
//========================================================================== | |
/// Matches a views left, right, top, or bottom edges to another view, with optional insets. | |
class func constraints(for view: Anchorable, toPinTo otherView: Anchorable, edges: UIRectEdge = .all, insets: UIEdgeInsets = .zero) -> [NSLayoutConstraint] { | |
var constraints: [NSLayoutConstraint] = [] | |
if edges.contains(.left) { | |
constraints.append(view.leftAnchor.constraint(equalTo: otherView.leftAnchor, constant: insets.left)) | |
} | |
if edges.contains(.right) { | |
constraints.append(view.rightAnchor.constraint(equalTo: otherView.rightAnchor, constant: -insets.right)) | |
} | |
if edges.contains(.top) { | |
constraints.append(view.topAnchor.constraint(equalTo: otherView.topAnchor, constant: insets.top)) | |
} | |
if edges.contains(.bottom) { | |
constraints.append(view.bottomAnchor.constraint(equalTo: otherView.bottomAnchor, constant: -insets.bottom)) | |
} | |
return constraints | |
} | |
/// Matches a views left, right, top, or bottom edges to another view, with optional insets. | |
@discardableResult class func activeConstraints(for view: Anchorable, toPinTo otherView: Anchorable, edges: UIRectEdge = .all, insets: UIEdgeInsets = .zero) -> [NSLayoutConstraint] { | |
let constraint = constraints(for: view, toPinTo: otherView, edges: edges, insets: insets) | |
activate(constraint) | |
return constraint | |
} | |
/// Matches a views width and height to another view, with optional insets. | |
class func constraints(for view: Anchorable, toMatchSizeOf otherView: Anchorable, insets: UIEdgeInsets = .zero) -> [NSLayoutConstraint] { | |
var constraints: [NSLayoutConstraint] = [] | |
constraints.append(view.widthAnchor.constraint(equalTo: otherView.widthAnchor, constant: -insets.horizontalInsets)) | |
constraints.append(view.heightAnchor.constraint(equalTo: otherView.heightAnchor, constant: -insets.verticalInsets)) | |
return constraints | |
} | |
/// Matches a views width and height to another view, with optional insets. | |
@discardableResult class func activeConstraints(for view: Anchorable, toMatchSizeOf otherView: Anchorable, insets: UIEdgeInsets = .zero) -> [NSLayoutConstraint] { | |
let constraint = constraints(for: view, toMatchSizeOf: otherView, insets: insets) | |
activate(constraint) | |
return constraint | |
} | |
func withPriority(_ priority: UILayoutPriority) -> NSLayoutConstraint { | |
self.priority = priority | |
return self | |
} | |
} | |
public protocol Anchorable { | |
var leadingAnchor: NSLayoutXAxisAnchor { get } | |
var trailingAnchor: NSLayoutXAxisAnchor { get } | |
var leftAnchor: NSLayoutXAxisAnchor { get } | |
var rightAnchor: NSLayoutXAxisAnchor { get } | |
var topAnchor: NSLayoutYAxisAnchor { get } | |
var bottomAnchor: NSLayoutYAxisAnchor { get } | |
var widthAnchor: NSLayoutDimension { get } | |
var heightAnchor: NSLayoutDimension { get } | |
var centerXAnchor: NSLayoutXAxisAnchor { get } | |
var centerYAnchor: NSLayoutYAxisAnchor { get } | |
} | |
extension UILayoutGuide: Anchorable {} | |
extension UIView: Anchorable {} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example Usage