Skip to content

Instantly share code, notes, and snippets.

@younata
Created December 19, 2020 21:02
Show Gist options
  • Save younata/3607b3a0478855f42bf11dec2f62c8b0 to your computer and use it in GitHub Desktop.
Save younata/3607b3a0478855f42bf11dec2f62c8b0 to your computer and use it in GitHub Desktop.
UIView Autolayout Helpers
import UIKit
extension UIView {
func removeAllSubviews() {
for view in self.subviews {
view.removeFromSuperview()
}
}
@discardableResult
func withContentCompressionResistance(_ priority: UILayoutPriority, for axis: NSLayoutConstraint.Axis) -> Self {
self.setContentCompressionResistancePriority(priority, for: axis)
return self
}
@discardableResult
func withContentHugging(_ priority: UILayoutPriority, for axis: NSLayoutConstraint.Axis) -> Self {
self.setContentHuggingPriority(priority, for: axis)
return self
}
@discardableResult
func centerInSuperview() -> Self {
guard let superview = self.superview else {
NSLog("Error: No superview to center in")
return self
}
self.translatesAutoresizingMaskIntoConstraints = false
self.centerYAnchor.constraint(equalTo: superview.centerYAnchor).isActive = true
self.centerXAnchor.constraint(equalTo: superview.centerXAnchor).isActive = true
return self
}
struct LayoutPriorities {
let top: UILayoutPriority
let left: UILayoutPriority
let bottom: UILayoutPriority
let right: UILayoutPriority
static let required: LayoutPriorities = {
return LayoutPriorities(top: .required, left: .required, bottom: .required, right: .required)
}()
}
public struct Edge: OptionSet {
public init(rawValue: Int) {
self.rawValue = rawValue
}
public let rawValue: Int
static let left = Edge(rawValue: 1 << 0)
static let right = Edge(rawValue: 1 << 1)
static let top = Edge(rawValue: 1 << 2)
static let bottom = Edge(rawValue: 1 << 3)
var attribute: NSLayoutConstraint.Attribute {
switch self {
case .left: return .leading
case .right: return .trailing
case .top: return .top
case .bottom: return .bottom
default: fatalError("Asked for attribute for \(self.rawValue) which is not mappable to any attribute")
}
}
}
@discardableResult
func fillUpSuperview(insets: UIEdgeInsets = .zero, priorities: LayoutPriorities = .required, except excludedEdge: Edge = []) -> Self {
guard let superview = self.superview else {
NSLog("Error: No superview to fill up")
return self
}
self.translatesAutoresizingMaskIntoConstraints = false
if excludedEdge.contains(.left) == false {
let leading = self.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: insets.left)
leading.priority = priorities.left
leading.isActive = true
}
if excludedEdge.contains(.right) == false {
let trailing = self.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: -insets.right)
trailing.priority = priorities.right
trailing.isActive = true
}
if excludedEdge.contains(.top) == false {
let top = self.topAnchor.constraint(equalTo: superview.topAnchor, constant: insets.top)
top.priority = priorities.top
top.isActive = true
}
if excludedEdge.contains(.bottom) == false {
let bottom = self.bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -insets.bottom)
bottom.priority = priorities.bottom
bottom.isActive = true
}
return self
}
@discardableResult
func pin(edge: Edge, to otherEdge: Edge, of view: UIView, offset: CGFloat = 0, priority: UILayoutPriority = .required) -> Self {
guard self.superview == view.superview, self.superview != nil else {
NSLog("Error: Can't pin edge to other edge, because they don't have a common parent")
return self
}
self.translatesAutoresizingMaskIntoConstraints = false
view.translatesAutoresizingMaskIntoConstraints = false
let constraint = NSLayoutConstraint(item: self, attribute: edge.attribute, relatedBy: .equal, toItem: view,
attribute: otherEdge.attribute, multiplier: 1.0, constant: offset)
constraint.priority = priority
self.superview?.addConstraint(constraint)
return self
}
public enum Dimension {
case horizontal
case vertical
fileprivate var layoutAttribute: NSLayoutConstraint.Attribute {
switch self {
case .horizontal: return .width
case .vertical: return .height
}
}
}
@discardableResult
func match(dimension: Dimension, toDimension otherDimension: Dimension, of view: UIView, offset: CGFloat = 0, multiplier: CGFloat = 1, priority: UILayoutPriority = .required) -> Self {
guard let sharedSuperview = self.sharedSuperview(with: view) else { return self }
self.translatesAutoresizingMaskIntoConstraints = false
let constraint = NSLayoutConstraint(item: self, attribute: dimension.layoutAttribute, relatedBy: .equal, toItem: superview, attribute: otherDimension.layoutAttribute, multiplier: multiplier, constant: offset)
constraint.priority = priority
sharedSuperview.addConstraint(constraint)
return self
}
@discardableResult
func set(dimension: Dimension, to offset: CGFloat, priority: UILayoutPriority = .required) -> Self {
self.addConstraint(NSLayoutConstraint(item: self, attribute: dimension.layoutAttribute, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: offset).with(\.priority, as: priority))
return self
}
private func sharedSuperview(with otherView: UIView) -> UIView? {
if self == otherView {
return self
}
if self.isChild(of: otherView) {
return otherView
}
if otherView.isChild(of: self) {
return self
}
let superviews = Set(self.parents())
var other = otherView
while superviews.contains(other) == false {
guard let otherSuper = other.superview else {
return nil
}
other = otherSuper
}
return other
}
private func isChild(of view: UIView) -> Bool {
if self == view { return true }
return self.superview?.isChild(of: view) ?? false
}
private func parents() -> [UIView] {
if let superview = self.superview {
return [superview] + superview.parents()
}
return []
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment