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

7.8 KiB

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

SwiftUI Style Guide with Design System Integration for DuckDuckGo Browser

View Structure

View Organization

struct FeatureView: View {
    // MARK: - Environment and State
    @Environment(\.colorScheme) var colorScheme
    @EnvironmentObject var appSettings: AppSettings
    
    // MARK: - State and Binding
    @State private var localState = false
    @Binding var externalState: Bool
    
    // MARK: - View Model
    @StateObject private var viewModel: FeatureViewModel
    
    // MARK: - Body
    var body: some View {
        content
            .onAppear { viewModel.onAppear() }
    }
    
    // MARK: - Subviews
    @ViewBuilder
    private var content: some View {
        // Main content here
    }
}

Design System Integration

REQUIRED: Use DesignResourcesKit Colors

ALWAYS use semantic colors from DesignResourcesKit instead of hardcoded or system colors:

// ✅ CORRECT - DesignResourcesKit semantic colors
Text("Title")
    .foregroundColor(Color(designSystemColor: .textPrimary))
    .background(Color(designSystemColor: .surface))

VStack {
    Rectangle()
        .fill(Color(designSystemColor: .accent))
    
    Button("Action") { }
        .foregroundColor(Color(designSystemColor: .controlsFillPrimary))
}
.background(Color(designSystemColor: .background))

// ❌ INCORRECT - Hardcoded or system colors
Text("Title")
    .foregroundColor(.black) // Don't use hardcoded colors
    .background(.gray) // Don't use system colors

// ❌ INCORRECT - Manual dark mode handling
@Environment(\.colorScheme) var colorScheme
let textColor = colorScheme == .dark ? Color.white : Color.black // Use semantic colors instead

REQUIRED: Use DesignResourcesKit Icons

ALWAYS use icons from DesignResourcesKitIcons package:

// ✅ CORRECT - DesignResourcesKit icons
Button(action: addAction) {
    Image(uiImage: DesignSystemImages.Glyphs.Size16.add)
        .foregroundColor(Color(designSystemColor: .accent))
}

Image(uiImage: DesignSystemImages.Color.Size24.bookmark)
    .resizable()
    .frame(width: 24, height: 24)

// ❌ INCORRECT - System or custom icons
Button(action: addAction) {
    Image(systemName: "plus") // Use DesignResourcesKit icons
}

Image("custom_icon") // Use DesignResourcesKit icons instead

Design System Color Categories

Use appropriate semantic color categories:

// Text colors
.foregroundColor(Color(designSystemColor: .textPrimary))
.foregroundColor(Color(designSystemColor: .textSecondary))
.foregroundColor(Color(designSystemColor: .textLink))

// Background colors
.background(Color(designSystemColor: .background))
.background(Color(designSystemColor: .surface))
.background(Color(designSystemColor: .panel))

// Control colors
.foregroundColor(Color(designSystemColor: .controlsFillPrimary))
.foregroundColor(Color(designSystemColor: .controlsFillSecondary))

// Button colors (use specific button color tokens)
.foregroundColor(Color(designSystemColor: .buttonPrimaryText))
.background(Color(designSystemColor: .buttonPrimaryBackground))

Typography with Design System

Use semantic typography that integrates with the design system:

// ✅ CORRECT - Design system typography
Text("Header")
    .font(.title2.weight(.semibold))
    .foregroundColor(Color(designSystemColor: .textPrimary))

Text("Body")
    .font(.body)
    .foregroundColor(Color(designSystemColor: .textSecondary))

Text("Caption")
    .font(.caption)
    .foregroundColor(Color(designSystemColor: .textSecondary))

// Platform-specific typography (macOS)
#if os(macOS)
Text("Preference Title")
    .font(Fonts.preferencePaneTitle)
    .foregroundColor(Color(designSystemColor: .textPrimary))
#endif

Theme Integration

Use Theme protocol for complex scenarios:

// ✅ CORRECT - Theme integration for advanced scenarios
struct ComplexView: View {
    @EnvironmentObject var themeManager: ThemeManager
    
    var body: some View {
        VStack {
            Text("Content")
                .foregroundColor(Color(themeManager.currentTheme.textColor))
        }
        .background(Color(themeManager.currentTheme.backgroundColor))
    }
}

// ✅ PREFERRED - Direct semantic colors for simple cases
struct SimpleView: View {
    var body: some View {
        Text("Content")
            .foregroundColor(Color(designSystemColor: .textPrimary))
            .background(Color(designSystemColor: .background))
    }
}

Component Patterns

Reusable Components

  • Create small, focused components
  • Use ViewModifiers for common styling
  • Leverage ViewBuilder for conditional content
struct PrimaryButton: View {
    let title: String
    let action: () -> Void
    
    var body: some View {
        Button(action: action) {
            Text(title)
                .foregroundColor(.white)
                .padding()
                .background(Color.accentColor)
                .cornerRadius(8)
        }
    }
}

Lists and Navigation

List {
    Section {
        ForEach(items) { item in
            NavigationLink(destination: DetailView(item: item)) {
                ItemRow(item: item)
            }
        }
    } header: {
        Text("Section Title")
    }
}
.listStyle(.insetGrouped)

State Management

View Model Pattern

class FeatureViewModel: ObservableObject {
    @Published var items: [Item] = []
    @Published var isLoading = false
    @Published var error: Error?
    
    func loadData() async {
        isLoading = true
        defer { isLoading = false }
        
        do {
            items = try await service.fetchItems()
        } catch {
            self.error = error
        }
    }
}

Async Operations

struct ContentView: View {
    @StateObject private var viewModel = ViewModel()
    
    var body: some View {
        content
            .task {
                await viewModel.loadData()
            }
            .refreshable {
                await viewModel.refresh()
            }
    }
}

Animations and Transitions

Smooth Animations

@State private var isExpanded = false

var body: some View {
    VStack {
        content
            .frame(height: isExpanded ? 200 : 100)
            .animation(.spring(), value: isExpanded)
    }
}

Custom Transitions

.transition(.asymmetric(
    insertion: .move(edge: .trailing).combined(with: .opacity),
    removal: .move(edge: .leading).combined(with: .opacity)
))

Accessibility

Always Include Accessibility

Image(systemName: "star.fill")
    .accessibilityLabel("Favorite")
    .accessibilityHint("Double tap to toggle favorite status")

Button(action: action) {
    Text("Submit")
}
.accessibilityIdentifier("submit_button")

Performance Considerations

Lazy Loading

ScrollView {
    LazyVStack {
        ForEach(items) { item in
            ItemView(item: item)
        }
    }
}

Avoid Expensive Operations in Body

// Bad
var body: some View {
    let processedData = expensiveOperation(data) // Don't do this
    Text(processedData)
}

// Good
@State private var processedData: String = ""

var body: some View {
    Text(processedData)
        .onAppear {
            processedData = expensiveOperation(data)
        }
}

Preview Support

Comprehensive Previews

struct FeatureView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            FeatureView()
                .previewDisplayName("Default")
            
            FeatureView()
                .preferredColorScheme(.dark)
                .previewDisplayName("Dark Mode")
            
            FeatureView()
                .previewDevice("iPhone SE (3rd generation)")
                .previewDisplayName("Small Device")
        }
    }
}