Last active
October 14, 2019 16:20
-
-
Save bradleymackey/11be187b84601405bfcbca7fb46b6965 to your computer and use it in GitHub Desktop.
Helpers for Autolayout I use in basically every project -- make Autolayout more bearable for you!
This file contains 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
// | |
// 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