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)