Agent Skills: SwiftUI Skill

Build modern UIs with SwiftUI - views, state management, animations, navigation

swiftswiftuiiosstate-managementnavigation
uiID: pluginagentmarketplace/custom-plugin-swift/swift-swiftui

Skill Files

Browse the full folder contents for swift-swiftui.

Download Skill

Loading file tree…

skills/swift-swiftui/SKILL.md

Skill Metadata

Name
swift-swiftui
Description
Build modern UIs with SwiftUI - views, state management, animations, navigation

SwiftUI Skill

Declarative UI framework knowledge for building modern Apple platform interfaces.

Prerequisites

  • Xcode 15+ installed
  • iOS 16+ / macOS 13+ deployment target recommended
  • Understanding of reactive programming concepts

Parameters

parameters:
  min_ios_version:
    type: string
    default: "16.0"
    description: Minimum iOS version
  platforms:
    type: array
    items: [iOS, macOS, watchOS, tvOS, visionOS]
    default: [iOS]
  observation_framework:
    type: string
    enum: [observation, combine, observable_object]
    default: observation
    description: State management approach

Topics Covered

Property Wrappers

| Wrapper | Ownership | Use Case | |---------|-----------|----------| | @State | View owns | Local, private state | | @Binding | Parent owns | Two-way child connection | | @StateObject | View creates/owns | Observable object lifecycle | | @ObservedObject | External owns | Passed observable | | @EnvironmentObject | Environment owns | Dependency injection | | @Environment | System provides | System values (colorScheme, etc) |

Observation (iOS 17+)

| Feature | Description | |---------|-------------| | @Observable | Macro for observable classes | | @Bindable | Create bindings from Observable | | Automatic tracking | No need for @Published |

Layout System

| Container | Purpose | |-----------|---------| | VStack | Vertical arrangement | | HStack | Horizontal arrangement | | ZStack | Overlapping views | | LazyVStack/HStack | Lazy loading for lists | | Grid | 2D grid layout | | GeometryReader | Access to size/position |

Code Examples

Observation Pattern (iOS 17+)

import SwiftUI

@Observable
final class ShoppingCart {
    var items: [CartItem] = []
    var couponCode: String = ""

    var subtotal: Decimal {
        items.reduce(0) { $0 + $1.price * Decimal($1.quantity) }
    }

    var total: Decimal {
        let discount = applyCoupon(to: subtotal)
        return subtotal - discount
    }

    func add(_ product: Product, quantity: Int = 1) {
        if let index = items.firstIndex(where: { $0.product.id == product.id }) {
            items[index].quantity += quantity
        } else {
            items.append(CartItem(product: product, quantity: quantity))
        }
    }

    func remove(_ item: CartItem) {
        items.removeAll { $0.id == item.id }
    }

    private func applyCoupon(to amount: Decimal) -> Decimal {
        guard !couponCode.isEmpty else { return 0 }
        // Apply coupon logic
        return amount * 0.1
    }
}

struct CartView: View {
    @Bindable var cart: ShoppingCart

    var body: some View {
        List {
            ForEach(cart.items) { item in
                CartItemRow(item: item)
            }
            .onDelete { indexSet in
                cart.items.remove(atOffsets: indexSet)
            }

            Section {
                HStack {
                    TextField("Coupon code", text: $cart.couponCode)
                    Button("Apply") { }
                }

                LabeledContent("Subtotal", value: cart.subtotal, format: .currency(code: "USD"))
                LabeledContent("Total", value: cart.total, format: .currency(code: "USD"))
                    .fontWeight(.bold)
            }
        }
        .navigationTitle("Cart (\(cart.items.count))")
    }
}

Custom View Modifier

struct CardStyle: ViewModifier {
    let cornerRadius: CGFloat
    let shadowRadius: CGFloat

    func body(content: Content) -> some View {
        content
            .background(.background)
            .clipShape(RoundedRectangle(cornerRadius: cornerRadius))
            .shadow(color: .black.opacity(0.1), radius: shadowRadius, y: 2)
    }
}

extension View {
    func cardStyle(cornerRadius: CGFloat = 12, shadowRadius: CGFloat = 4) -> some View {
        modifier(CardStyle(cornerRadius: cornerRadius, shadowRadius: shadowRadius))
    }
}

// Usage
struct ProductCard: View {
    let product: Product

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            AsyncImage(url: product.imageURL) { image in
                image.resizable().aspectRatio(contentMode: .fill)
            } placeholder: {
                ProgressView()
            }
            .frame(height: 150)
            .clipped()

            Text(product.name)
                .font(.headline)

            Text(product.price, format: .currency(code: "USD"))
                .foregroundStyle(.secondary)
        }
        .cardStyle()
    }
}

Custom Animations

struct PulsingButton: View {
    let title: String
    let action: () -> Void

    @State private var isPulsing = false

    var body: some View {
        Button(action: action) {
            Text(title)
                .font(.headline)
                .foregroundStyle(.white)
                .padding(.horizontal, 24)
                .padding(.vertical, 12)
                .background(.blue)
                .clipShape(Capsule())
                .scaleEffect(isPulsing ? 1.05 : 1.0)
        }
        .onAppear {
            withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) {
                isPulsing = true
            }
        }
    }
}

struct MatchedGeometryExample: View {
    @Namespace private var animation
    @State private var isExpanded = false

    var body: some View {
        VStack {
            if isExpanded {
                RoundedRectangle(cornerRadius: 20)
                    .fill(.blue)
                    .matchedGeometryEffect(id: "shape", in: animation)
                    .frame(height: 300)
            } else {
                RoundedRectangle(cornerRadius: 10)
                    .fill(.blue)
                    .matchedGeometryEffect(id: "shape", in: animation)
                    .frame(width: 100, height: 100)
            }
        }
        .onTapGesture {
            withAnimation(.spring(response: 0.5, dampingFraction: 0.7)) {
                isExpanded.toggle()
            }
        }
    }
}

Navigation Stack (iOS 16+)

struct NavigationExample: View {
    @State private var path = NavigationPath()

    var body: some View {
        NavigationStack(path: $path) {
            List(products) { product in
                NavigationLink(value: product) {
                    ProductRow(product: product)
                }
            }
            .navigationTitle("Products")
            .navigationDestination(for: Product.self) { product in
                ProductDetailView(product: product)
            }
            .navigationDestination(for: Category.self) { category in
                CategoryView(category: category)
            }
        }
    }

    func navigateToProduct(_ product: Product) {
        path.append(product)
    }

    func popToRoot() {
        path.removeLast(path.count)
    }
}

Troubleshooting

Common Issues

| Issue | Cause | Solution | |-------|-------|----------| | View not updating | Wrong property wrapper | Check ownership: @State vs @StateObject | | Preview crash | Missing mock data | Provide preview with sample data | | Animation stutters | Expensive body | Extract subviews, avoid complex calculations | | Navigation broken | Missing NavigationStack | Ensure view is inside NavigationStack | | List slow | Complex cells | Use LazyVStack, simplify cell views |

Debug Tips

// Trace view updates
var body: some View {
    let _ = Self._printChanges()
    // ... view content
}

// Check if preview
#if DEBUG
struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView(data: .preview)
    }
}
#endif

Validation Rules

validation:
  - rule: state_ownership
    severity: error
    check: @StateObject for views that create, @ObservedObject for passed
  - rule: body_purity
    severity: warning
    check: No side effects in body computed property
  - rule: lazy_for_lists
    severity: info
    check: Use LazyVStack/LazyHStack for long scrolling content

Usage

Skill("swift-swiftui")

Related Skills

  • swift-combine - Reactive programming
  • swift-uikit - UIKit interop
  • swift-architecture - MVVM patterns