Created
December 19, 2020 21:02
-
-
Save younata/3607b3a0478855f42bf11dec2f62c8b0 to your computer and use it in GitHub Desktop.
UIView Autolayout Helpers
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
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