SwiftUI Components
Instructions
- Analyze the required component functionality
- Check existing components for reuse
- Apply project styling conventions
- Ensure accessibility compliance
- Add preview providers
Component Patterns
View with ViewModel
import SwiftUI
struct FeatureView: View {
@State private var viewModel = FeatureViewModel()
var body: some View {
NavigationStack {
content
.navigationTitle("Feature")
.task { await viewModel.load() }
}
}
@ViewBuilder
private var content: some View {
if viewModel.isLoading {
ProgressView()
} else {
List(viewModel.items) { item in
ItemRow(item: item)
}
}
}
}
#Preview {
FeatureView()
}
@Observable ViewModel
import Foundation
@Observable
final class FeatureViewModel {
var items: [Item] = []
var isLoading = false
var error: Error?
private let service: ItemServiceProtocol
init(service: ItemServiceProtocol = ItemService()) {
self.service = service
}
func load() async {
isLoading = true
defer { isLoading = false }
do {
items = try await service.fetchItems()
} catch {
self.error = error
}
}
}
Custom View Modifier
struct CardModifier: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(.background)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(radius: 2)
}
}
extension View {
func card() -> some View {
modifier(CardModifier())
}
}
Reusable Button Style
struct PrimaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.headline)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding()
.background(configuration.isPressed ? Color.accentColor.opacity(0.8) : Color.accentColor)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
extension ButtonStyle where Self == PrimaryButtonStyle {
static var primary: PrimaryButtonStyle { PrimaryButtonStyle() }
}
Best Practices
- Extract views when they exceed 100 lines
- Use
@ViewBuilderfor conditional content - Prefer composition over inheritance
- Always add accessibility labels
- Test on multiple device sizes