7.7 KiB
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.