Injecting Declarative Genes into UIKit - DSL Construction Practices Based on @resultBuilder

Declarative Revolution for UIKit: Applying @resultBuilder in UIStackView - SwiftlyUI

The @resultBuilder feature introduced in iOS 13 opened a ​​new paradigm of metaprogramming​​ for Swift. Its greatest value lies in enabling declarative expression capabilities for traditional UIKit views, particularly optimizing container components like UIStackView that rely on subview arrangement.

​​Core Implementation Principles​​:

  • Create custom result builders (e.g., ViewBuilder)
  • Extend UIStackView initialization methods
  • Manage arrangedSubviews through closure syntactic sugar
// DSL-driven initialization approach
let stack = UIStackView(axis: .vertical) {
    UILabel("Title")
    UIImageView(systemName: "star.fill")
    if showButton { 
        SubmitButton()
    }
}

DSL Core Implementation: ViewBuilder's Complete Result Builder Protocol Implementation Showcases Three Technical Trade-offs

@resultBuilder
public struct ViewBuilder {
    public static func buildBlock(_ components: [UIView]...) -> [UIView] { components.flatMap { $0 } }
    public static func buildBlock() -> [UIView] { [] }
    public static func buildOptional(_ component: [UIView]?) -> [UIView] { component ?? [] }
    public static func buildArray(_ components: [[UIView]]) -> [UIView] { components.flatMap { $0 } }
    public static func buildEither(first: [UIView]) -> [UIView] { first }
    public static func buildPartialBlock(accumulated: [UIView], next: [UIView]) -> [UIView] { accumulated + next }
    ...
}

View Container Implementation Strategy: Encapsulating UIStackView

  • Force-fixed axis properties to avoid runtime unintended modifications
  • Default .center alignment balances layout flexibility
  • Preserves native UIStackView API accessibility
@available(iOS 13, *)
public final class VStackView: UIStackView {
    public override var axis: NSLayoutConstraint.Axis {
        get { .vertical }
        set { super.axis = .vertical }
    }
    
    @discardableResult
    public convenience init(spacing: CGFloat = 0, @ViewBuilder content: () -> [UIView]) {
        self.init(frame: .zero)
        self.axis = .vertical
        self.spacing = spacing
        self.alignment = .center
        content().forEach { addArrangedSubview($0) }
    }
}

@available(iOS 13, *)
public final class HStackView: UIStackView { ... }

@available(iOS 13, *)
public final class ZStackView: UIView {
    public convenience init(@ViewBuilder content: () -> [UIView]) {
        self.init(frame: .zero)
        let views = content()
        
        // Disable Auto Layout flags
        setLayoutActivation(false, for: views)
        
        // Add subviews
        views.forEach { addSubview($0) }
        
        // Re-enable layout constraints
        setLayoutActivation(true, for: views)
        views.forEach { $0.safeActivateConstraints() }
        
        // Apply safe area constraints
        views.forEach { applyAlignmentConstraints(for: $0) }
    }
    
    private func setLayoutActivation(_ enabled: Bool, for views: [UIView]) {
        views.forEach {
            $0.canActiveLayout = enabled
            setLayoutActivation(enabled, for: $0.subviews)
        }
    }
    
    private func applyAlignmentConstraints(for view: UIView) {
        let guide = layoutMarginsGuide
        
        // Create flexible margin constraints (priority reduced to avoid conflicts)
        view.addNewConstraint(
            leadingAnchor.constraint(greaterThanOrEqualTo: guide.leadingAnchor),
            type: .marginsLeft
        ).priority = .defaultLow
        
        // Other direction constraints follow similar logic...
    }
}

Fluent API Design Specifications - Extend UIView to Provide Elegant Configuration Interfaces:

public extension UIView {
    @discardableResult
    func opacity(_ value: CGFloat) -> Self {
        self.alpha = value
        return self
    }
    
    @discardableResult
    func alpha(_ value: CGFloat) -> Self {
        self.alpha = value
        return self
    }
  
    @discardableResult
    func tag(_ tag: Int) -> Self {
        self.tag = tag
        return self
    }
    ...
}

Autolayout System Encapsulation - Constraint Storage Architecture

extension UIView {
    // Constraint repository
    struct ConstraintHolder {
        var constraints: [ConstraintType: NSLayoutConstraint] = [:]
        var pending: [ConstraintType: ConstraintConfig] = [:]
    }
    
    // Constraint configuration model
    struct ConstraintConfig {
        let type: ConstraintType
        let targetType: ConstraintTargetType
        let offset: CGFloat
        let multiplier: CGFloat
        let relation: NSLayoutConstraint.Relation
        let anchor: Any? // NSLayoutXAxisAnchor | NSLayoutYAxisAnchor | NSLayoutDimension
        let usesMargins: Bool
        
        // Complete constructor...
    }

    func addNewConstraint(_ constraint: NSLayoutConstraint, type: ConstraintType) {
        translatesAutoresizingMaskIntoConstraints = false
        
        // Remove old constraints
        removeConstraint(type: type)
        
        var holder = constraintHolder
        if superview != nil {
            constraint.isActive = true // Auto-activate valid constraints
        }
        holder.constraints[type] = constraint
        constraintHolder = holder
    }
}

Constraint Engine Operations

@discardableResult
    func trailing(to anchor: NSLayoutXAxisAnchor, offset: CGFloat = 0) -> Self {
        let config = ConstraintConfig(type: .trailing, targetType: .other, offset: offset, XAxisAnchor: anchor)
        var holder = constraintHolder
        holder.pendingConstraints[.trailing] = config
        constraintHolder = holder
        return self
    }
    
    @discardableResult
    func trailing(to view: UIView, offset: CGFloat = 0) -> Self {
        if view == superview {
            addNewConstraint(
                trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: -offset),
                type: .trailing
            )
        }else {
            trailing(to: view.layoutMarginsGuide.trailingAnchor, offset: offset)
        }
        return self
    }

Complete DSL Development Paradigm - Practical Application: Login Form Implementation

let loginForm = ZStackView { // Equivalent to UIView
    UIView()
        .frame(width: 300, height: 200)
        .backgroundColor(.red.opacity(0.5))
        .fillSuperMargins()
    
    VStackView(spacing: 10) {
        HStackView(spacing: 10) {
            Label("Username:")
                .font(.medium(14))
                .width(50)
            
            UITextField("Enter username")
                .height(35)
                .width(180)
        }
        .border(.orange)
        .cornerRadius(5)
        
        HStackView(spacing: 10) {
            Label("Password:")
                .font(.medium(14))
                .width(50)
            
            UITextField("Enter password")
                .height(35)
                .width(180)
        }
        .border(.orange)
        .cornerRadius(5)
    }
    .distribution(.fillEqually)
    .centerToSuper()
}
.backgroundColor(.blue.opacity(0.5))
.padding(10)
.center(to: view)

view.addSubview(loginForm)
1 Like