Created
June 7, 2024 04:12
-
-
Save rijieli/cfc08860e60f77b1d69d98d5f94aab44 to your computer and use it in GitHub Desktop.
UIView+LayoutBridge.swift
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
// | |
// 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