Skip to content

Instantly share code, notes, and snippets.

@kean
Last active Nov 4, 2021
Embed
What would you like to do?
public protocol LayoutItem { // `UIView`, `UILayoutGuide`
var superview: UIView? { get }
}
extension UIView: LayoutItem {}
extension UILayoutGuide: LayoutItem {
public var superview: UIView? { owningView }
}
public struct Alignment {
public enum Horizontal {
case fill, center, leading, trailing
}
public enum Vertical {
case fill, center, top, bottom
}
public let horizontal: Horizontal
public let vertical: Vertical
public init(horizontal: Horizontal, vertical: Vertical) {
(self.horizontal, self.vertical) = (horizontal, vertical)
}
public static let fill = Alignment(horizontal: .fill, vertical: .fill)
public static let center = Alignment(horizontal: .center, vertical: .center)
public static let topLeading = Alignment(horizontal: .leading, vertical: .top)
public static let leading = Alignment(horizontal: .leading, vertical: .fill)
public static let bottomLeading = Alignment(horizontal: .leading, vertical: .bottom)
public static let bottom = Alignment(horizontal: .fill, vertical: .bottom)
public static let bottomTrailing = Alignment(horizontal: .trailing, vertical: .bottom)
public static let trailing = Alignment(horizontal: .trailing, vertical: .fill)
public static let topTrailing = Alignment(horizontal: .trailing, vertical: .top)
public static let top = Alignment(horizontal: .fill, vertical: .top)
}
public extension LayoutItem {
/// Pins the edges to the edges of the given item. By default, pins the edges
/// to the superview.
///
/// - parameter target: The target view, by default, uses the superview.
/// - parameter insets: Insets the reciever's edges by the given insets.
/// - parameter axis: If provided, creates constraints only along the given
/// axis. For example, if you pass axis `.horizontal`, only the `.leading`,
/// `.trailing` (and `.centerX` if needed) attributes are used. `nil` by default
/// - parameter alignment: `.fill` by default, see `Alignment` for a list of
/// the available options.
@discardableResult
func pinEdges(to item2: LayoutItem? = nil, insets: UIEdgeInsets = .zero, axis: NSLayoutConstraint.Axis? = nil, alignment: Alignment = .fill, priority: UILayoutPriority? = nil) -> [NSLayoutConstraint] {
assert(Thread.isMainThread, "Align APIs can only be used from the main thread")
(self as? UIView)?.translatesAutoresizingMaskIntoConstraints = false
guard let item2 = item2 ?? self.superview else {
assertionFailure("View is not part of the view hierarhcy")
return []
}
var constraints = [NSLayoutConstraint]()
func constrain(attribute: NSLayoutConstraint.Attribute, relation: NSLayoutConstraint.Relation, constant: CGFloat) {
let constraint = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: item2, attribute: attribute, multiplier: 1, constant: constant)
if let priority = priority {
constraint.priority = priority
}
constraints.append(constraint)
}
if axis == nil || axis == .horizontal {
constrain(attribute: .leading, relation: alignment.horizontal == .fill || alignment.horizontal == .leading ? .equal : .greaterThanOrEqual, constant: insets.left)
constrain(attribute: .trailing, relation: alignment.horizontal == .fill || alignment.horizontal == .trailing ? .equal : .lessThanOrEqual, constant: -insets.right)
if alignment.horizontal == .center {
constrain(attribute: .centerX, relation: .equal, constant: 0)
}
}
if axis == nil || axis == .vertical {
constrain(attribute: .top, relation: alignment.vertical == .fill || alignment.vertical == .top ? .equal : .greaterThanOrEqual, constant: insets.top)
constrain(attribute: .bottom, relation: alignment.vertical == .fill || alignment.vertical == .bottom ? .equal : .lessThanOrEqual, constant: -insets.bottom)
if alignment.vertical == .center {
constrain(attribute: .centerY, relation: .equal, constant: 0)
}
}
NSLayoutConstraint.activate(constraints)
return constraints
}
}

Pin Edges

This file contains pinEdges() method extracted from Align. This is the most powerful and flexible API in Align. The rest of the features can be replaced with native layout anchors.

view.pinEdges(insets: 20)

// Same as the following:
view.pinEdges(
    to: view.superview!
    insets: EdgeInsets(top: 20, left: 20, bottom: 20, trailing: 20),
    alignment: .fill
)

pin edges

By default, pinEdges() method pin the edges to the superview of the current view. However, you can select any target view or layout guide:

// Pin to superview
view.pinEdges()

// Pin to layout margins guide
view.pinEdges(to: container.layoutMarginsGuide)

// Pin to safe area
view.pinEdges(to: container.safeAreaLayoutGuide)

Align also provides a convenient way to access anchors of the layout guide: view.anchors.safeArea.top.

By default, pin() users .fill alignment. There are variety of other alignments available (81 if you combine all possible options).

view.pinEdges(insets: 20, alignment: .center)

pin edges with center alignmnet

You can create constraint along the given axis.

view.pinEdges(insets: 20, axis: .horizontal, alignment: .center)

pin edges with center alignmnet for horizontal axis

Or pin the view to to a corner.

view.pinEdges(insets: 20, alignment: .topLeading)

pin edges with center alignmnet for horizontal axis

You can create custom alignments (see Alignment type) by providing a vertical and horizontal component.

anchors.pinEdges(insets: 20, alignment: Alignment(vertical: .center, horizontal: .leading))

pin edges with center alignmnet for horizontal axis

@kean
Copy link
Author

kean commented Jan 15, 2021

A simplified version of https://github.com/kean/Align which only has its most powerful method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment