Files
obsidian-vault/work/wiki/apple-browsers/swiftui-advanced.md
T

7.7 KiB

source, confidence, namespace, last_synced, alwaysApply
source confidence namespace last_synced alwaysApply
~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/swiftui-advanced.mdc 0.9 work 2026-04-28 false

Advanced SwiftUI Patterns

ViewModifier Composition

Create reusable ViewModifiers for common styling:

// ✅ ADVANCED - Composable ViewModifiers
struct DuckDuckGoButtonStyle: ViewModifier {
    let style: ButtonStyleType
    
    func body(content: Content) -> some View {
        content
            .padding(.horizontal, 16)
            .padding(.vertical, 12)
            .background(backgroundColorForStyle())
            .foregroundColor(textColorForStyle())
            .cornerRadius(8)
            .font(.body.weight(.medium))
    }
    
    private func backgroundColorForStyle() -> Color {
        switch style {
        case .primary:
            return Color(designSystemColor: .buttonPrimaryBackground)
        case .secondary:
            return Color(designSystemColor: .buttonSecondaryBackground)
        case .ghost:
            return .clear
        }
    }
    
    private func textColorForStyle() -> Color {
        switch style {
        case .primary:
            return Color(designSystemColor: .buttonPrimaryText)
        case .secondary:
            return Color(designSystemColor: .buttonSecondaryText)
        case .ghost:
            return Color(designSystemColor: .textLink)
        }
    }
}

// Usage
extension View {
    func duckDuckGoButtonStyle(_ style: ButtonStyleType) -> some View {
        modifier(DuckDuckGoButtonStyle(style: style))
    }
}

PreferenceKey for Cross-View Communication

Use PreferenceKey for sophisticated view communication:

// ✅ ADVANCED - PreferenceKey for collecting data from child views
struct ViewSizePreferenceKey: PreferenceKey {
    static var defaultValue: [String: CGSize] = [:]
    
    static func reduce(value: inout [String: CGSize], nextValue: () -> [String: CGSize]) {
        value.merge(nextValue()) { $1 }
    }
}

struct SizeReportingView<Content: View>: View {
    let id: String
    let content: Content
    
    init(id: String, @ViewBuilder content: () -> Content) {
        self.id = id
        self.content = content()
    }
    
    var body: some View {
        content
            .background(
                GeometryReader { geometry in
                    Color.clear
                        .preference(key: ViewSizePreferenceKey.self, 
                                  value: [id: geometry.size])
                }
            )
    }
}

// Usage
struct ParentView: View {
    @State private var childSizes: [String: CGSize] = [:]
    
    var body: some View {
        VStack {
            SizeReportingView(id: "header") {
                HeaderView()
            }
            
            SizeReportingView(id: "content") {
                ContentView()
            }
        }
        .onPreferenceChange(ViewSizePreferenceKey.self) { sizes in
            self.childSizes = sizes
        }
    }
}

Environment-based Dependency Injection

Use SwiftUI Environment for dependency injection:

// ✅ ADVANCED - Environment-based DI
struct DependencyProviderKey: EnvironmentKey {
    static let defaultValue: DependencyProvider = AppDependencyProvider.shared
}

extension EnvironmentValues {
    var dependencies: DependencyProvider {
        get { self[DependencyProviderKey.self] }
        set { self[DependencyProviderKey.self] = newValue }
    }
}

// Usage in views
struct FeatureView: View {
    @Environment(\.dependencies) var dependencies
    @StateObject private var viewModel: FeatureViewModel
    
    init() {
        // Note: This approach has limitations - see ios-architecture.md for preferred DI pattern
    }
    
    var body: some View {
        // View implementation
    }
}

Complex Animation Patterns

Use sophisticated animations for better UX:

// ✅ ADVANCED - Coordinated animations
struct TabSwitcherView: View {
    @State private var selectedTab: Int = 0
    @State private var animationPhase: AnimationPhase = .idle
    
    enum AnimationPhase {
        case idle, switching, settled
    }
    
    var body: some View {
        VStack {
            TabPickerView(selectedTab: $selectedTab)
                .animation(.easeInOut(duration: 0.3), value: selectedTab)
            
            TabContent(selectedTab: selectedTab)
                .transition(.asymmetric(
                    insertion: .move(edge: .trailing).combined(with: .opacity),
                    removal: .move(edge: .leading).combined(with: .opacity)
                ))
                .animation(.spring(response: 0.6, dampingFraction: 0.8), value: selectedTab)
        }
        .onChange(of: selectedTab) { newValue in
            withAnimation(.easeInOut(duration: 0.1)) {
                animationPhase = .switching
            }
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                withAnimation(.easeOut(duration: 0.1)) {
                    animationPhase = .settled
                }
            }
        }
    }
}

Custom Layout with Layout Protocol (iOS 16+)

Create sophisticated layouts:

// ✅ ADVANCED - Custom layout for complex arrangements
@available(iOS 16.0, macOS 13.0, *)
struct FlexibleGrid: Layout {
    let spacing: CGFloat
    let itemSize: CGSize
    
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        let containerWidth = proposal.width ?? 300
        let itemsPerRow = max(1, Int(containerWidth / (itemSize.width + spacing)))
        let rows = (subviews.count + itemsPerRow - 1) / itemsPerRow
        
        let totalWidth = CGFloat(itemsPerRow) * itemSize.width + CGFloat(itemsPerRow - 1) * spacing
        let totalHeight = CGFloat(rows) * itemSize.height + CGFloat(rows - 1) * spacing
        
        return CGSize(width: totalWidth, height: totalHeight)
    }
    
    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        let containerWidth = bounds.width
        let itemsPerRow = max(1, Int(containerWidth / (itemSize.width + spacing)))
        
        for (index, subview) in subviews.enumerated() {
            let row = index / itemsPerRow
            let column = index % itemsPerRow
            
            let x = bounds.minX + CGFloat(column) * (itemSize.width + spacing)
            let y = bounds.minY + CGFloat(row) * (itemSize.height + spacing)
            
            subview.place(at: CGPoint(x: x, y: y), proposal: ProposedViewSize(itemSize))
        }
    }
}

State Management with @Observable (iOS 17+)

Use the modern @Observable macro when available:

// ✅ MODERN - @Observable for iOS 17+
@available(iOS 17.0, macOS 14.0, *)
@Observable
final class ModernViewModel {
    var items: [Item] = []
    var isLoading = false
    var selectedItem: Item?
    
    private let service: FeatureServiceProtocol
    
    init(service: FeatureServiceProtocol) {
        self.service = service
    }
    
    @MainActor
    func loadData() async {
        isLoading = true
        defer { isLoading = false }
        
        do {
            items = try await service.fetchItems()
        } catch {
            // Handle error
        }
    }
}

// Usage
struct ModernFeatureView: View {
    @State private var viewModel: ModernViewModel
    
    init(service: FeatureServiceProtocol) {
        self._viewModel = State(initialValue: ModernViewModel(service: service))
    }
    
    var body: some View {
        List(viewModel.items, selection: $viewModel.selectedItem) { item in
            ItemRow(item: item)
        }
        .task {
            await viewModel.loadData()
        }
    }
}

See swiftui-style.md for basic SwiftUI patterns and design system integration.