Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Helpers for Autolayout I use in basically every project -- make Autolayout more bearable for you!
//
// UIView+Constraints.swift
//
// Created by Bradley Mackey on 30/06/2019.
// Copyright © 2019 Bradley Mackey. All rights reserved.
//
/*
* Abstract: AutoLayout helpers when programmatically defining views
*/
import UIKit
/// When a view is edge using a helper method, the binding constraints are returned in this object such that they can be later manipulated
struct EdgeConstraints {
let top: NSLayoutConstraint
let bottom: NSLayoutConstraint
let leading: NSLayoutConstraint
let trailing: NSLayoutConstraint
init(
_ top: NSLayoutConstraint,
_ bottom: NSLayoutConstraint,
_ leading: NSLayoutConstraint,
_ trailing: NSLayoutConstraint
) {
self.top = top
self.bottom = bottom
self.leading = leading
self.trailing = trailing
}
var allConstraints: [NSLayoutConstraint] {
return [top, bottom, leading, trailing]
}
}
extension UIView {
/// make so this view will not be compressed with autolayout
func setAutolayoutConcrete(for axis: NSLayoutConstraint.Axis) {
setContentHuggingPriority(.init(1000), for: axis)
setContentCompressionResistancePriority(.init(1000), for: axis)
}
func setAutolayoutFlexible(for axis: NSLayoutConstraint.Axis) {
setContentHuggingPriority(.init(1), for: axis)
setContentCompressionResistancePriority(.init(1), for: axis)
}
/// a stretchy spacer view, allows other views to wrap their content inside of a stack view easily
static var spacer: UIView {
let view = UIView()
view.backgroundColor = .clear
view.translatesAutoresizingMaskIntoConstraints = false
view.setAutolayoutFlexible(for: .horizontal)
view.setAutolayoutFlexible(for: .vertical)
return view
}
/// a fixed spacer of a specific height
static func spacer(height: CGFloat) -> UIView {
let view = UIView()
view.height(equalTo: height)
return view
}
/// a fixed spacer of a specific width
static func spacer(width: CGFloat) -> UIView {
let view = UIView()
view.width(equalTo: width)
return view
}
/// constrain this view to have a height equal to the supplied view, with the given multiplier
@discardableResult
func height(equalTo view: UIView, multiplier: CGFloat = 1) -> NSLayoutConstraint {
let constraint = heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: multiplier)
constraint.isActive = true
return constraint
}
/// wrapper for autolayout constraint
@discardableResult
func height(equalTo constant: CGFloat) -> NSLayoutConstraint {
let constraint = heightAnchor.constraint(equalToConstant: constant)
constraint.isActive = true
return constraint
}
/// constrain this view to have an equal width to the the supplied view, with the given multiplier
@discardableResult
func width(equalTo view: UIView, multiplier: CGFloat = 1) -> NSLayoutConstraint {
let constraint = widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: multiplier)
constraint.isActive = true
return constraint
}
/// wrapper for autolayout constraint
@discardableResult
func width(equalTo constant: CGFloat) -> NSLayoutConstraint {
let constraint = widthAnchor.constraint(equalToConstant: constant)
constraint.isActive = true
return constraint
}
/// make this view have an equal width and height to the supplied view
@discardableResult
func size(equalTo view: UIView) -> (height: NSLayoutConstraint, width: NSLayoutConstraint) {
let h = height(equalTo: view)
let w = width(equalTo: view)
return (h, w)
}
/// Adds a view as a subview and constrains it to the edges
/// of its new containing view.
///
/// - Parameter view: view to add as subview and constrain
@discardableResult
func addEdgeConstrainedSubview(view: UIView) -> EdgeConstraints {
addSubview(view)
return edgeConstrain(subview: view)
}
func centerConstrain(subview: UIView, xOffset: CGFloat = 0, yOffset: CGFloat = 0) {
let x = subview.centerXAnchor.constraint(equalTo: centerXAnchor, constant: xOffset)
let y = subview.centerYAnchor.constraint(equalTo: centerYAnchor, constant: yOffset)
NSLayoutConstraint.activate([x, y])
}
/// Constrains a given subview to all 4 sides
/// of its containing view with a constant of 0.
///
/// - Parameter subView: view to constrain to its container
/// - parameter margin: how inset this should be (default 0)
/// - returns: the edge constraints that were supplied, so these can be manipulated later on if required
@discardableResult
func edgeConstrain(subview: UIView, inset: UIEdgeInsets = .zero, priority: UILayoutPriority = .required) -> EdgeConstraints {
subview.translatesAutoresizingMaskIntoConstraints = false
let top = subview.topAnchor.constraint(equalTo: topAnchor, constant: inset.top)
let bottom = subview.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -inset.bottom)
let leading = subview.leadingAnchor.constraint(equalTo: leadingAnchor, constant: inset.left)
let trailing = subview.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -inset.right)
let edgeConstraints = EdgeConstraints(top, bottom, leading, trailing)
edgeConstraints.allConstraints.forEach { cons in
cons.priority = priority
}
NSLayoutConstraint.activate(edgeConstraints.allConstraints)
return edgeConstraints
}
@discardableResult
func edgeConstrainToSafeArea(subview: UIView, inset: UIEdgeInsets = .zero) -> EdgeConstraints {
subview.translatesAutoresizingMaskIntoConstraints = false
let top = subview.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: inset.top)
let bottom = subview.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -inset.bottom)
let leading = subview.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: inset.left)
let trailing = subview.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -inset.right)
NSLayoutConstraint.activate([ top, bottom, leading, trailing ])
return EdgeConstraints(top, bottom, leading, trailing)
}
/// for constraining the calendar view, the left (leading) is where the hour labels are positioned, so these should always be inset by the safe area, but the rest of the calendar should stretch past the safe area
@discardableResult
func edgeConstrainToLeftSafeAreaOnly(subview: UIView, inset: UIEdgeInsets = .zero) -> EdgeConstraints {
subview.translatesAutoresizingMaskIntoConstraints = false
let top = subview.topAnchor.constraint(equalTo: topAnchor, constant: inset.top)
let bottom = subview.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -inset.bottom)
let leading = subview.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: inset.left)
let trailing = subview.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -inset.right)
NSLayoutConstraint.activate([ top, bottom, leading, trailing ])
return EdgeConstraints(top, bottom, leading, trailing)
}
@discardableResult
func edgeConstrainAsSquare(subview: UIView, inset: UIEdgeInsets = .zero) -> EdgeConstraints {
subview.translatesAutoresizingMaskIntoConstraints = false
setContentHuggingPriority(.init(1), for: .vertical)
setContentHuggingPriority(.init(1), for: .horizontal)
let edgeConstraints = edgeConstrain(subview: subview, inset: inset, priority: .defaultLow)
// also constrain to be a square in the middle
let height = subview.heightAnchor.constraint(equalTo: subview.widthAnchor, multiplier: 1)
height.priority = .required
height.isActive = true
let centerx = centerXAnchor.constraint(equalTo: subview.centerXAnchor)
centerx.priority = .required
centerx.isActive = true
let centery = centerYAnchor.constraint(equalTo: subview.centerYAnchor)
centery.priority = .required
centery.isActive = true
return edgeConstraints
}
/// adds visual constraint format to the given viewss
func addConstraintsWithFormat(_ format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
view.translatesAutoresizingMaskIntoConstraints = false
viewsDictionary[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: viewsDictionary))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment