Skip to content

Instantly share code, notes, and snippets.

@rijieli
Created June 7, 2024 04:12
Show Gist options
  • Save rijieli/cfc08860e60f77b1d69d98d5f94aab44 to your computer and use it in GitHub Desktop.
Save rijieli/cfc08860e60f77b1d69d98d5f94aab44 to your computer and use it in GitHub Desktop.
UIView+LayoutBridge.swift
//
// UIView+LayoutBridge.swift
// IdeasForm
//
// Created by Roger on 2022/4/1.
// Copyright © 2022 IdeasForm. All rights reserved.
//
import Foundation
import UIKit
/// A bridge to UIView or UILayoutGuide 's anchor, use `.labr` to fetch instance.
protocol LayoutAnchorBridge {
/// Center X
var x: NSLayoutXAxisAnchor { get }
/// Center Y
var y: NSLayoutYAxisAnchor { get }
var width: NSLayoutDimension { get }
var height: NSLayoutDimension { get }
var leading: NSLayoutXAxisAnchor { get }
var trailing: NSLayoutXAxisAnchor { get }
var top: NSLayoutYAxisAnchor { get }
var bottom: NSLayoutYAxisAnchor { get }
func done()
// NEXTUP: LayoutAnchorBridge .labr.sup.box()
// Ignore add superview code
}
// MARK: - Layout methods original define.
extension LayoutAnchorBridge {
/// Add height and width **constant** constraints to view.
@discardableResult
func size(_ width: CGFloat? = nil, _ height: CGFloat? = nil, priority: Float? = nil) -> Self {
var constraintsToApply: [NSLayoutConstraint] = []
if let width = width {
constraintsToApply.append(self.width.constraint(equalToConstant: width))
}
if let height = height {
constraintsToApply.append(self.height.constraint(equalToConstant: height))
}
if let priority = priority {
constraintsToApply.forEach {
$0.priority = .init(rawValue: priority)
}
}
NSLayoutConstraint.activate(constraintsToApply)
return self
}
func box(_ layoutGuide: LayoutAnchorBridge, priority: Float? = nil,spacing: CGFloat?...) -> Self {
assert(spacing.count == 4, "Layout define incomplete, use <nil> represent for undefined constraint.")
var constraintsToApply: [NSLayoutConstraint] = []
let (vLeading, vTrailing, vTop, vBottom) = (spacing[0], spacing[1], spacing[2], spacing[3])
if let vLeading = vLeading {
constraintsToApply.append(self.leading.constraint(equalTo: layoutGuide.leading, constant: vLeading))
}
if let vTrailing = vTrailing {
constraintsToApply.append(self.trailing.constraint(equalTo: layoutGuide.trailing, constant: -vTrailing))
}
if let vTop = vTop {
constraintsToApply.append(self.top.constraint(equalTo: layoutGuide.top, constant: vTop))
}
if let vBottom = vBottom {
constraintsToApply.append(self.bottom.constraint(equalTo: layoutGuide.bottom, constant: -vBottom))
}
if let priority = priority {
constraintsToApply.forEach { $0.priority = .init(rawValue: priority) }
}
NSLayoutConstraint.activate(constraintsToApply)
return self
}
/// Layout for x or y axis position.
func axis(_ relateView: LayoutAnchorBridge,x xVal: CGFloat? = nil,y yVal: CGFloat? = nil) -> Self {
var constraintsToApply: [NSLayoutConstraint] = []
if let xVal = xVal {
constraintsToApply.append(self.x.constraint(equalTo: relateView.x, constant: xVal))
}
if let yVal = yVal {
constraintsToApply.append(self.y.constraint(equalTo: relateView.y, constant: yVal))
}
NSLayoutConstraint.activate(constraintsToApply)
return self
}
func above(_ relateView: LayoutAnchorBridge, _ spacing: CGFloat) -> Self {
NSLayoutConstraint.activate([self.bottom.constraint(equalTo: relateView.top, constant: -spacing)])
return self
}
func under(_ relateView: LayoutAnchorBridge, spacing: CGFloat) -> Self {
NSLayoutConstraint.activate([self.top.constraint(equalTo: relateView.bottom, constant: spacing)])
return self
}
func after(_ relateView: LayoutAnchorBridge, spacing: CGFloat) -> Self {
NSLayoutConstraint.activate([self.leading.constraint(equalTo: relateView.trailing, constant: spacing)])
return self
}
func before(_ relateView: LayoutAnchorBridge, spacing: CGFloat) -> Self {
NSLayoutConstraint.activate([self.trailing.constraint(equalTo: relateView.leading, constant: -spacing)])
return self
}
/// Layout suffix to add custom constraints.
func custom(_ constraints: NSLayoutConstraint...) -> Self {
NSLayoutConstraint.activate(constraints)
return self
}
func custom(_ constraints: [NSLayoutConstraint], priority: Float? = nil) -> Self {
if let priority = priority {
constraints.forEach { $0.priority = .init(rawValue: priority) }
}
NSLayoutConstraint.activate(constraints)
return self
}
func sameSize(to rhs: LayoutAnchorBridge, h hVal: CGFloat? = nil, w wVal: CGFloat? = nil) -> Self {
var constraintsToApply: [NSLayoutConstraint] = []
if hVal != nil {
constraintsToApply.append(self.height.constraint(equalTo: rhs.height, constant: hVal ?? 0))
}
if wVal != nil {
constraintsToApply.append(self.width.constraint(equalTo: rhs.width, constant: wVal ?? 0))
}
NSLayoutConstraint.activate(constraintsToApply)
return self
}
}
// MARK: - Layout methods reload
extension LayoutAnchorBridge {
/// Add leading, trailing, top, bottom constraint to view, use nil to skip constraint.
/// - parameters:
/// - defines: "leading trailing top bottom" and nil to skip.
/// - relateView: always be parameter equalTo
/// - returns: UIView it self as returned value.
func box(_ relateView: UIView, priority: Float? = nil, spacing: CGFloat?...) -> Self {
assert(spacing.count == 4, "Layout define incomplete, use <nil> represent for undefined constraint.")
return box(relateView.labr, priority: priority, spacing: spacing[0], spacing[1], spacing[2], spacing[3])
}
func box(_ relateView: UIView, priority: Float? = nil, insets: UIEdgeInsets) -> Self {
return box(relateView.labr, priority: priority, spacing: insets.left, insets.right, insets.top, insets.bottom)
}
func box(_ relateView: UILayoutGuide, priority: Float? = nil, spacing: CGFloat?...) -> Self {
assert(spacing.count == 4, "Layout define incomplete, use <nil> represent for undefined constraint.")
return box(relateView.labr, priority: priority, spacing: spacing[0], spacing[1], spacing[2], spacing[3])
}
/// Equal padding box layout bridge
func box(_ realteView: UIView, padding: CGFloat) -> Self {
return box(realteView, spacing: padding, padding, padding, padding)
}
func box(_ realteView: UILayoutGuide, padding: CGFloat) -> Self {
return box(realteView, spacing: padding, padding, padding, padding)
}
/// Add leading, trailing, top, bottom constraint to view, use nil to skip constraint.
/// - parameters:
/// - defines: "leading trailing top bottom" and nil to skip.
/// - relateView: always be parameter equalTo
/// - returns: UIView it self as returned value.
func box(_ relateView: UILayoutGuide, spacing: CGFloat?...) -> Self {
assert(spacing.count == 4, "Layout define incomplete, use <nil> represent for undefined constraint.")
return box(relateView.labr, spacing: spacing[0], spacing[1], spacing[2], spacing[3])
}
/// Layout for x or y axis position.
func axis(_ relateView: UIView, x: CGFloat? = nil, y: CGFloat? = nil) -> Self { axis(relateView.labr, x: x, y: y) }
func axis(_ relateView: UILayoutGuide, x: CGFloat? = nil, y: CGFloat? = nil) -> Self { axis(relateView.labr, x: x, y: y) }
func above(_ relateView: UIView, _ spacing: CGFloat) -> Self { above(relateView.labr, spacing) }
func above(_ relateView: UILayoutGuide, _ spacing: CGFloat) -> Self { above(relateView.labr, spacing) }
func after(_ relateView: UIView, _ spacing: CGFloat) -> Self { after(relateView.labr, spacing: spacing) }
func after(_ relateView: UILayoutGuide, _ spacing: CGFloat) -> Self { after(relateView.labr, spacing: spacing) }
func before(_ relateView: UIView, _ spacing: CGFloat) -> Self { before(relateView.labr, spacing: spacing) }
func before(_ relateView: UILayoutGuide, _ spacing: CGFloat) -> Self { before(relateView.labr, spacing: spacing) }
func under(_ relateView: UIView, _ spacing: CGFloat) -> Self { under(relateView.labr, spacing: spacing) }
func under(_ relateView: UILayoutGuide, _ spacing: CGFloat) -> Self { under(relateView.labr, spacing: spacing) }
}
// MARK: - Implement of layout bridge.
extension UIView {
var labr: LayoutAnchorBridge { return LayoutBridgeUIView(self) }
}
extension UILayoutGuide {
var labr: LayoutAnchorBridge { return LayoutBridgeUILayoutGuide(self) }
}
class LayoutBridgeUIView: LayoutAnchorBridge {
var x: NSLayoutXAxisAnchor { ownedView.centerXAnchor }
var y: NSLayoutYAxisAnchor { ownedView.centerYAnchor }
var width: NSLayoutDimension { ownedView.widthAnchor }
var height: NSLayoutDimension { ownedView.heightAnchor }
var leading: NSLayoutXAxisAnchor { ownedView.leadingAnchor }
var trailing: NSLayoutXAxisAnchor { ownedView.trailingAnchor }
var top: NSLayoutYAxisAnchor { ownedView.topAnchor }
var bottom: NSLayoutYAxisAnchor { ownedView.bottomAnchor }
private let ownedView: UIView
init(_ owned: UIView) { self.ownedView = owned }
/// translatesAutoresizingMaskIntoConstraints = false
func done() {
if self.ownedView.translatesAutoresizingMaskIntoConstraints {
self.ownedView.translatesAutoresizingMaskIntoConstraints = false
}
}
}
class LayoutBridgeUILayoutGuide: LayoutAnchorBridge {
var x: NSLayoutXAxisAnchor { ownedView.centerXAnchor }
var y: NSLayoutYAxisAnchor { ownedView.centerYAnchor }
var width: NSLayoutDimension { ownedView.widthAnchor }
var height: NSLayoutDimension { ownedView.heightAnchor }
var leading: NSLayoutXAxisAnchor { ownedView.leadingAnchor }
var trailing: NSLayoutXAxisAnchor { ownedView.trailingAnchor }
var top: NSLayoutYAxisAnchor { ownedView.topAnchor }
var bottom: NSLayoutYAxisAnchor { ownedView.bottomAnchor }
private let ownedView: UILayoutGuide
init(_ owned: UILayoutGuide) { self.ownedView = owned }
func done() {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment