session-ios/SessionUIKit/Utilities/UIView+Constraints.swift

217 lines
7.9 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
// MARK: - Enums
public protocol ConstraintUtilitiesEdge {}
public extension UIView {
enum HorizontalEdge: ConstraintUtilitiesEdge { case left, leading, right, trailing }
enum VerticalEdge: ConstraintUtilitiesEdge { case top, bottom }
enum Direction { case horizontal, vertical }
enum Dimension { case width, height }
}
// MARK: - Anchorable
public protocol Anchorable {
func anchor(from edge: UIView.HorizontalEdge) -> NSLayoutXAxisAnchor
func anchor(from edge: UIView.VerticalEdge) -> NSLayoutYAxisAnchor
}
extension UIView: Anchorable {
public func anchor(from edge: UIView.HorizontalEdge) -> NSLayoutXAxisAnchor {
switch edge {
case .left: return leftAnchor
case .leading: return leadingAnchor
case .right: return rightAnchor
case .trailing: return trailingAnchor
}
}
public func anchor(from edge: UIView.VerticalEdge) -> NSLayoutYAxisAnchor {
switch edge {
case .top: return topAnchor
case .bottom: return bottomAnchor
}
}
}
extension UILayoutGuide: Anchorable {
public func anchor(from edge: UIView.HorizontalEdge) -> NSLayoutXAxisAnchor {
switch edge {
case .left: return leftAnchor
case .leading: return leadingAnchor
case .right: return rightAnchor
case .trailing: return trailingAnchor
}
}
public func anchor(from edge: UIView.VerticalEdge) -> NSLayoutYAxisAnchor {
switch edge {
case .top: return topAnchor
case .bottom: return bottomAnchor
}
}
}
fileprivate extension NSLayoutConstraint {
func setting(isActive: Bool) -> NSLayoutConstraint {
self.isActive = isActive
return self
}
}
public extension Anchorable {
@discardableResult
func pin(_ constraineeEdge: UIView.HorizontalEdge, to constrainerEdge: UIView.HorizontalEdge, of anchorable: Anchorable, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
(self as? UIView)?.translatesAutoresizingMaskIntoConstraints = false
return anchor(from: constraineeEdge)
.constraint(
equalTo: anchorable.anchor(from: constrainerEdge),
constant: inset
)
.setting(isActive: true)
}
@discardableResult
func pin(_ constraineeEdge: UIView.HorizontalEdge, greaterThanOrEqualTo constrainerEdge: UIView.HorizontalEdge, of anchorable: Anchorable, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
(self as? UIView)?.translatesAutoresizingMaskIntoConstraints = false
return anchor(from: constraineeEdge)
.constraint(
greaterThanOrEqualTo: anchorable.anchor(from: constrainerEdge),
constant: inset
)
.setting(isActive: true)
}
@discardableResult
func pin(_ constraineeEdge: UIView.VerticalEdge, to constrainerEdge: UIView.VerticalEdge, of anchorable: Anchorable, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
(self as? UIView)?.translatesAutoresizingMaskIntoConstraints = false
return anchor(from: constraineeEdge)
.constraint(
equalTo: anchorable.anchor(from: constrainerEdge),
constant: inset
)
.setting(isActive: true)
}
@discardableResult
func pin(_ constraineeEdge: UIView.VerticalEdge, greaterThanOrEqualTo constrainerEdge: UIView.VerticalEdge, of anchorable: Anchorable, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
(self as? UIView)?.translatesAutoresizingMaskIntoConstraints = false
return anchor(from: constraineeEdge)
.constraint(
greaterThanOrEqualTo: anchorable.anchor(from: constrainerEdge),
constant: inset
)
.setting(isActive: true)
}
}
// MARK: - View extensions
public extension UIView {
func pin(_ edges: [ConstraintUtilitiesEdge], to view: UIView) {
edges.forEach {
switch $0 {
case let edge as HorizontalEdge: pin(edge, to: edge, of: view)
case let edge as VerticalEdge: pin(edge, to: edge, of: view)
default: break
}
}
}
func pin(to view: UIView) {
[ HorizontalEdge.leading, HorizontalEdge.trailing ].forEach { pin($0, to: $0, of: view) }
[ VerticalEdge.top, VerticalEdge.bottom ].forEach { pin($0, to: $0, of: view) }
}
func pin(to view: UIView, withInset inset: CGFloat) {
pin(.leading, to: .leading, of: view, withInset: inset)
pin(.top, to: .top, of: view, withInset: inset)
view.pin(.trailing, to: .trailing, of: self, withInset: inset)
view.pin(.bottom, to: .bottom, of: self, withInset: inset)
}
@discardableResult
func center(_ direction: Direction, in view: UIView, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false
let constraint: NSLayoutConstraint = {
switch direction {
case .horizontal: return centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: inset)
case .vertical: return centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: inset)
}
}()
constraint.isActive = true
return constraint
}
func center(in view: UIView) {
center(.horizontal, in: view)
center(.vertical, in: view)
}
@discardableResult
func set(_ dimension: Dimension, to size: CGFloat) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false
let constraint: NSLayoutConstraint = {
switch dimension {
case .width: return widthAnchor.constraint(equalToConstant: size)
case .height: return heightAnchor.constraint(equalToConstant: size)
}
}()
constraint.isActive = true
return constraint
}
@discardableResult
func set(_ dimension: Dimension, to otherDimension: Dimension, of view: UIView, withOffset offset: CGFloat = 0, multiplier: CGFloat = 1) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false
let otherAnchor: NSLayoutDimension = {
switch otherDimension {
case .width: return view.widthAnchor
case .height: return view.heightAnchor
}
}()
let constraint: NSLayoutConstraint = {
switch dimension {
case .width: return widthAnchor.constraint(equalTo: otherAnchor, multiplier: multiplier, constant: offset)
case .height: return heightAnchor.constraint(equalTo: otherAnchor, multiplier: multiplier, constant: offset)
}
}()
constraint.isActive = true
return constraint
}
@discardableResult
func set(_ dimension: Dimension, greaterThanOrEqualTo size: CGFloat) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false
let constraint: NSLayoutConstraint = {
switch dimension {
case .width: return widthAnchor.constraint(greaterThanOrEqualToConstant: size)
case .height: return heightAnchor.constraint(greaterThanOrEqualToConstant: size)
}
}()
constraint.isActive = true
return constraint
}
@discardableResult
func set(_ dimension: Dimension, lessThanOrEqualTo size: CGFloat) -> NSLayoutConstraint {
translatesAutoresizingMaskIntoConstraints = false
let constraint: NSLayoutConstraint = {
switch dimension {
case .width: return widthAnchor.constraint(lessThanOrEqualToConstant: size)
case .height: return heightAnchor.constraint(lessThanOrEqualToConstant: size)
}
}()
constraint.isActive = true
return constraint
}
}