# SwiftUI Patterns Modern SwiftUI patterns for iOS 26 with iOS 18 compatibility. ## View Composition ### Small, Focused Views ```swift // Bad: Massive view struct ContentView: View { var body: some View { VStack { // 200 lines of UI code } } } // Good: Composed from smaller views struct ContentView: View { var body: some View { VStack { HeaderView() ItemList() ActionBar() } } } struct HeaderView: View { var body: some View { // Focused implementation } } ``` ### Extract Subviews ```swift struct ItemRow: View { let item: Item var body: some View { HStack { iconView contentView Spacer() chevronView } } private var iconView: some View { Image(systemName: item.icon) .foregroundStyle(.accent) .frame(width: 30) } private var contentView: some View { VStack(alignment: .leading) { Text(item.name) .font(.headline) Text(item.subtitle) .font(.caption) .foregroundStyle(.secondary) } } private var chevronView: some View { Image(systemName: "chevron.right") .foregroundStyle(.tertiary) .font(.caption) } } ``` ## Async Data Loading ### Task Modifier ```swift struct ItemList: View { @State private var items: [Item] = [] @State private var isLoading = true @State private var error: Error? var body: some View { Group { if isLoading { ProgressView() } else if let error { ErrorView(error: error, retry: load) } else { List(items) { item in ItemRow(item: item) } } } .task { await load() } } private func load() async { isLoading = true defer { isLoading = false } do { items = try await fetchItems() } catch { self.error = error } } } ``` ### Refresh Control ```swift struct ItemList: View { @State private var items: [Item] = [] var body: some View { List(items) { item in ItemRow(item: item) } .refreshable { items = try? await fetchItems() } } } ``` ### Task with ID Reload when identifier changes: ```swift struct ItemDetail: View { let itemID: UUID @State private var item: Item? var body: some View { Group { if let item { ItemContent(item: item) } else { ProgressView() } } .task(id: itemID) { item = try? await fetchItem(id: itemID) } } } ``` ## Lists and Grids ### Swipe Actions ```swift List { ForEach(items) { item in ItemRow(item: item) .swipeActions(edge: .trailing) { Button(role: .destructive) { delete(item) } label: { Label("Delete", systemImage: "trash") } Button { archive(item) } label: { Label("Archive", systemImage: "archivebox") } .tint(.orange) } .swipeActions(edge: .leading) { Button { toggleFavorite(item) } label: { Label("Favorite", systemImage: item.isFavorite ? "star.fill" : "star") } .tint(.yellow) } } } ``` ### Lazy Grids ```swift struct PhotoGrid: View { let photos: [Photo] let columns = [GridItem(.adaptive(minimum: 100), spacing: 2)] var body: some View { ScrollView { LazyVGrid(columns: columns, spacing: 2) { ForEach(photos) { photo in AsyncImage(url: photo.thumbnailURL) { image in image .resizable() .aspectRatio(1, contentMode: .fill) } placeholder: { Color.gray.opacity(0.3) } .clipped() } } } } } ``` ### Sections with Headers ```swift List { ForEach(groupedItems, id: \.key) { section in Section(section.key) { ForEach(section.items) { item in ItemRow(item: item) } } } } .listStyle(.insetGrouped) ``` ## Forms and Input ### Form with Validation ```swift struct ProfileForm: View { @State private var name = "" @State private var email = "" @State private var bio = "" private var isValid: Bool { !name.isEmpty && email.contains("@") && email.contains(".") } var body: some View { Form { Section("Personal Info") { TextField("Name", text: $name) .textContentType(.name) TextField("Email", text: $email) .textContentType(.emailAddress) .keyboardType(.emailAddress) .autocapitalization(.none) } Section("About") { TextField("Bio", text: $bio, axis: .vertical) .lineLimit(3...6) } Section { Button("Save") { save() } .disabled(!isValid) } } } } ``` ### Pickers ```swift struct SettingsView: View { @State private var selectedTheme = Theme.system @State private var fontSize = 16.0 var body: some View { Form { Picker("Theme", selection: $selectedTheme) { ForEach(Theme.allCases) { theme in Text(theme.rawValue).tag(theme) } } Section("Text Size") { Slider(value: $fontSize, in: 12...24, step: 1) { Text("Font Size") } minimumValueLabel: { Text("A").font(.caption) } maximumValueLabel: { Text("A").font(.title) } .padding(.vertical) } } } } ``` ## Sheets and Alerts ### Sheet Presentation ```swift struct ContentView: View { @State private var showingSettings = false @State private var selectedItem: Item? var body: some View { List(items) { item in Button(item.name) { selectedItem = item } } .toolbar { Button { showingSettings = true } label: { Image(systemName: "gear") } } .sheet(isPresented: $showingSettings) { SettingsView() } .sheet(item: $selectedItem) { item in ItemDetail(item: item) } } } ``` ### Confirmation Dialogs ```swift struct ItemRow: View { let item: Item @State private var showingDeleteConfirmation = false var body: some View { HStack { Text(item.name) Spacer() Button(role: .destructive) { showingDeleteConfirmation = true } label: { Image(systemName: "trash") } } .confirmationDialog( "Delete \(item.name)?", isPresented: $showingDeleteConfirmation, titleVisibility: .visible ) { Button("Delete", role: .destructive) { delete(item) } } message: { Text("This action cannot be undone.") } } } ``` ## iOS 26 Features ### Liquid Glass ```swift struct GlassCard: View { var body: some View { VStack { Text("Premium Content") .font(.headline) } .padding() .background(.regularMaterial) .clipShape(RoundedRectangle(cornerRadius: 16)) // iOS 26 glass effect .glassEffect() } } // Availability check struct AdaptiveCard: View { var body: some View { if #available(iOS 26, *) { GlassCard() } else { StandardCard() } } } ``` ### WebView ```swift import WebKit // iOS 26+ native WebView struct WebContent: View { let url: URL var body: some View { if #available(iOS 26, *) { WebView(url: url) .ignoresSafeArea() } else { WebViewRepresentable(url: url) } } } // Fallback for iOS 18 struct WebViewRepresentable: UIViewRepresentable { let url: URL func makeUIView(context: Context) -> WKWebView { WKWebView() } func updateUIView(_ webView: WKWebView, context: Context) { webView.load(URLRequest(url: url)) } } ``` ### @Animatable Macro ```swift // iOS 26+ @available(iOS 26, *) @Animatable struct PulsingCircle: View { var scale: Double var body: some View { Circle() .scaleEffect(scale) } } ``` ## Custom Modifiers ### Reusable Styling ```swift struct CardModifier: ViewModifier { func body(content: Content) -> some View { content .padding() .background(.background) .clipShape(RoundedRectangle(cornerRadius: 12)) .shadow(color: .black.opacity(0.1), radius: 4, y: 2) } } extension View { func cardStyle() -> some View { modifier(CardModifier()) } } // Usage Text("Content") .cardStyle() ``` ### Conditional Modifiers ```swift extension View { @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { if condition { transform(self) } else { self } } } // Usage Text("Item") .if(isHighlighted) { view in view.foregroundStyle(.accent) } ``` ## Preview Techniques ### Multiple Configurations ```swift #Preview("Light Mode") { ItemRow(item: .sample) .preferredColorScheme(.light) } #Preview("Dark Mode") { ItemRow(item: .sample) .preferredColorScheme(.dark) } #Preview("Large Text") { ItemRow(item: .sample) .environment(\.sizeCategory, .accessibilityExtraLarge) } ``` ### Interactive Previews ```swift #Preview { @Previewable @State var isOn = false Toggle("Setting", isOn: $isOn) .padding() } ``` ### Preview with Mock Data ```swift extension Item { static let sample = Item( name: "Sample Item", subtitle: "Sample subtitle", icon: "star" ) static let samples: [Item] = [ Item(name: "First", subtitle: "One", icon: "1.circle"), Item(name: "Second", subtitle: "Two", icon: "2.circle"), Item(name: "Third", subtitle: "Three", icon: "3.circle") ] } #Preview { List(Item.samples) { item in ItemRow(item: item) } } ```