mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			403 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			403 lines
		
	
	
		
			16 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 HorizontalMargin: ConstraintUtilitiesEdge { case left, leading, right, trailing }
 | |
|     enum VerticalMargin: ConstraintUtilitiesEdge { case top, bottom }
 | |
|     enum Direction { case horizontal, vertical }
 | |
|     enum VerticalDirection { case vertical }
 | |
|     enum HorizontalDirection { case horizontal }
 | |
|     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
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public func attribute(from edge: UIView.HorizontalEdge) -> NSLayoutConstraint.Attribute {
 | |
|         switch edge {
 | |
|             case .left: return .left
 | |
|             case .leading: return .leading
 | |
|             case .right: return .right
 | |
|             case .trailing: return .trailing
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public func attribute(from edge: UIView.HorizontalMargin) -> NSLayoutConstraint.Attribute {
 | |
|         switch edge {
 | |
|             case .left: return .leftMargin
 | |
|             case .leading: return .leadingMargin
 | |
|             case .right: return .rightMargin
 | |
|             case .trailing: return .trailingMargin
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public func attribute(from edge: UIView.VerticalEdge) -> NSLayoutConstraint.Attribute {
 | |
|         switch edge {
 | |
|             case .top: return .top
 | |
|             case .bottom: return .bottom
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public func attribute(from edge: UIView.VerticalMargin) -> NSLayoutConstraint.Attribute {
 | |
|         switch edge {
 | |
|             case .top: return .topMargin
 | |
|             case .bottom: return .bottomMargin
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| public extension NSLayoutConstraint {
 | |
|     @discardableResult
 | |
|     func setting(isActive: Bool) -> NSLayoutConstraint {
 | |
|         self.isActive = isActive
 | |
|         return self
 | |
|     }
 | |
|     
 | |
|     @discardableResult
 | |
|     func setting(priority: UILayoutPriority) -> NSLayoutConstraint {
 | |
|         self.priority = priority
 | |
|         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.HorizontalEdge, lessThanOrEqualTo constrainerEdge: UIView.HorizontalEdge, of anchorable: Anchorable, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
 | |
|         (self as? UIView)?.translatesAutoresizingMaskIntoConstraints = false
 | |
|         
 | |
|         return anchor(from: constraineeEdge)
 | |
|             .constraint(
 | |
|                 lessThanOrEqualTo: 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)
 | |
|     }
 | |
|     
 | |
|     @discardableResult
 | |
|     func pin(_ constraineeEdge: UIView.VerticalEdge, lessThanOrEqualTo constrainerEdge: UIView.VerticalEdge, of anchorable: Anchorable, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
 | |
|         (self as? UIView)?.translatesAutoresizingMaskIntoConstraints = false
 | |
|         
 | |
|         return anchor(from: constraineeEdge)
 | |
|             .constraint(
 | |
|                 lessThanOrEqualTo: 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 pin(_ constraineeEdge: UIView.HorizontalEdge, toMargin constrainerMargin: UIView.HorizontalMargin, of constrainerView: UIView, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
 | |
|         translatesAutoresizingMaskIntoConstraints = false
 | |
|         
 | |
|         return NSLayoutConstraint(
 | |
|             item: self,
 | |
|             attribute: attribute(from: constraineeEdge),
 | |
|             relatedBy: .equal,
 | |
|             toItem: constrainerView,
 | |
|             attribute: constrainerView.attribute(from: constrainerMargin),
 | |
|             multiplier: 1,
 | |
|             constant: inset
 | |
|         )
 | |
|         .setting(isActive: true)
 | |
|     }
 | |
|     
 | |
|     @discardableResult
 | |
|     func pin(_ constraineeEdge: UIView.VerticalEdge, toMargin constrainerMargin: UIView.VerticalMargin, of constrainerView: UIView, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
 | |
|         translatesAutoresizingMaskIntoConstraints = false
 | |
|         
 | |
|         return NSLayoutConstraint(
 | |
|             item: self,
 | |
|             attribute: attribute(from: constraineeEdge),
 | |
|             relatedBy: .equal,
 | |
|             toItem: constrainerView,
 | |
|             attribute: constrainerView.attribute(from: constrainerMargin),
 | |
|             multiplier: 1,
 | |
|             constant: inset
 | |
|         )
 | |
|         .setting(isActive: true)
 | |
|     }
 | |
|     
 | |
|     func pin(toMarginsOf view: UIView) {
 | |
|         pin(.top, toMargin: .top, of: view)
 | |
|         pin(.leading, toMargin: .leading, of: view)
 | |
|         pin(.trailing, toMargin: .trailing, of: view)
 | |
|         pin(.bottom, toMargin: .bottom, of: view)
 | |
|     }
 | |
|     
 | |
|     @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 center(_ direction: VerticalDirection, against constrainerEdge: UIView.VerticalEdge, of anchorable: Anchorable, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
 | |
|         translatesAutoresizingMaskIntoConstraints = false
 | |
|         
 | |
|         return centerYAnchor
 | |
|             .constraint(equalTo: anchorable.anchor(from: constrainerEdge), constant: inset)
 | |
|             .setting(isActive: true)
 | |
|     }
 | |
|     
 | |
|     @discardableResult
 | |
|     func center(_ direction: HorizontalDirection, against constrainerEdge: UIView.HorizontalEdge, of anchorable: Anchorable, withInset inset: CGFloat = 0) -> NSLayoutConstraint {
 | |
|         translatesAutoresizingMaskIntoConstraints = false
 | |
|         
 | |
|         return centerXAnchor
 | |
|             .constraint(equalTo: anchorable.anchor(from: constrainerEdge), constant: inset)
 | |
|             .setting(isActive: true)
 | |
|     }
 | |
|     
 | |
|     @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, greaterThanOrEqualTo 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(greaterThanOrEqualTo: otherAnchor, multiplier: multiplier, constant: offset)
 | |
|             case .height: return heightAnchor.constraint(greaterThanOrEqualTo: otherAnchor, multiplier: multiplier, constant: offset)
 | |
|             }
 | |
|         }()
 | |
|         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
 | |
|     }
 | |
|     
 | |
|     @discardableResult
 | |
|     func set(_ dimension: Dimension, lessThanOrEqualTo 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(lessThanOrEqualTo: otherAnchor, multiplier: multiplier, constant: offset)
 | |
|             case .height: return heightAnchor.constraint(lessThanOrEqualTo: otherAnchor, multiplier: multiplier, constant: offset)
 | |
|             }
 | |
|         }()
 | |
|         constraint.isActive = true
 | |
|         return constraint
 | |
|     }
 | |
|     
 | |
|     func setContentHugging(to priority: UILayoutPriority) {
 | |
|         setContentHuggingPriority(priority, for: .vertical)
 | |
|         setContentHuggingPriority(priority, for: .horizontal)
 | |
|     }
 | |
|     
 | |
|     func setContentHugging(_ direction: Direction, to priority: UILayoutPriority) {
 | |
|         switch direction {
 | |
|             case .vertical: setContentHuggingPriority(priority, for: .vertical)
 | |
|             case .horizontal: setContentHuggingPriority(priority, for: .horizontal)
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     func setCompressionResistance(to priority: UILayoutPriority) {
 | |
|         setContentCompressionResistancePriority(priority, for: .vertical)
 | |
|         setContentCompressionResistancePriority(priority, for: .horizontal)
 | |
|     }
 | |
|     
 | |
|     func setCompressionResistance(_ direction: Direction, to priority: UILayoutPriority) {
 | |
|         switch direction {
 | |
|             case .vertical: setContentCompressionResistancePriority(priority, for: .vertical)
 | |
|             case .horizontal: setContentCompressionResistancePriority(priority, for: .horizontal)
 | |
|         }
 | |
|     }
 | |
| }
 |