| name | swift-patterns |
| description | SwiftUI view composition, @Observable patterns, async/await concurrency, TCA architecture, and Combine reactive streams. |
Swift Patterns
Modern Swift patterns for iOS/macOS application development.
SwiftUI View Composition
struct ProductCard: View {
let product: Product
var body: some View {
VStack(alignment: .leading, spacing: 8) {
ProductImage(url: product.imageURL)
ProductInfo(name: product.name, price: product.price)
RatingStars(rating: product.rating, count: product.reviewCount)
}
.padding()
.background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
struct ProductImage: View {
let url: URL
var body: some View {
AsyncImage(url: url) { phase in
switch phase {
case .success(let image):
image.resizable().aspectRatio(contentMode: .fill)
case .failure:
Image(systemName: "photo").foregroundStyle(.secondary)
case .empty:
ProgressView()
@unknown default:
EmptyView()
}
}
.frame(height: 200)
.clipped()
}
}
struct CardModifier: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(radius: 2)
}
}
extension View {
func cardStyle() -> some View {
modifier(CardModifier())
}
}
@Observable Pattern (iOS 17+)
import Observation
@Observable
final class ProductStore {
var products: [Product] = []
var isLoading = false
var errorMessage: String?
private let apiClient: APIClient
init(apiClient: APIClient = .shared) {
self.apiClient = apiClient
}
func loadProducts() async {
isLoading = true
errorMessage = nil
do {
products = try await apiClient.fetchProducts()
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
func deleteProduct(_ product: Product) async throws {
try await apiClient.deleteProduct(id: product.id)
products.removeAll { $0.id == product.id }
}
}
struct : {
store ()
body: {
(store.products) { product
(product: product)
}
.overlay {
store.isLoading { () }
error store.errorMessage {
(, systemImage: ,
description: (error))
}
}
.task { store.loadProducts() }
}
}
Structured Concurrency
func loadDashboard() async throws -> Dashboard {
async let profile = apiClient.fetchProfile()
async let orders = apiClient.fetchRecentOrders()
async let recommendations = apiClient.fetchRecommendations()
return try await Dashboard(
profile: profile,
orders: orders,
recommendations: recommendations
)
}
func loadImages(urls: [URL]) async -> [URL: UIImage] {
await withTaskGroup(of: (URL, UIImage?).self) { group in
for url in urls {
group.addTask {
let image = try? await ImageLoader.load(url)
return (url, image)
}
}
var results: [URL: UIImage] = [:]
for await (url, image) in group {
image { results[url] image }
}
results
}
}
{
cache: [: ] [:]
inFlight: [: <, >] [:]
( : ) -> {
cached cache[url] { cached }
existing inFlight[url] {
existing.value
}
task {
(data, ) .shared.data(from: url)
image (data: data) {
.invalidData
}
image
}
inFlight[url] task
image task.value
cache[url] image
inFlight[url]
image
}
}
TCA (The Composable Architecture) Pattern
import ComposableArchitecture
@Reducer
struct ProductFeature {
@ObservableState
struct State: Equatable {
var products: [Product] = []
var isLoading = false
var alert: AlertState<Action>?
}
enum Action {
case onAppear
case productsLoaded(Result<[Product], Error>)
case deleteProduct(Product)
case alertDismissed
}
@Dependency(\.apiClient) var apiClient
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .onAppear:
state.isLoading = true
return .run { send in
let result = await Result { try await apiClient.fetchProducts() }
await send(.productsLoaded(result))
}
case .productsLoaded(.success( products)):
state.isLoading
state.products products
.none
.productsLoaded(.failure( error)):
state.isLoading
state.alert { (error.localizedDescription) }
.none
.deleteProduct( product):
state.products.removeAll { .id product.id }
.run { apiClient.deleteProduct(id: product.id) }
.alertDismissed:
state.alert
.none
}
}
}
}
Checklist
Anti-Patterns
- Massive views: 200+ line body property (extract subviews)
- @StateObject in child views: use @State or pass as parameter
- Blocking main thread with synchronous work in views
- Force unwrapping optionals: use guard let or nil coalescing
- Ignoring task cancellation: leaked work after view disappears
- Using singletons instead of dependency injection (untestable)