Views & Modifiers
SwiftUI views are values, not objects: lightweight structs the framework re-renders cheaply. They compose with chaining (`.padding().background(.thinMaterial)`), respect dynamic type and dark mode automatically, and live alongside UIKit/AppKit via UIViewRepresentable/NSViewRepresentable. Mastering view modifiers is most of mastering SwiftUI.
View composition, modifiers, navigation, and accessibility
EXAMPLE
import SwiftUI
// 1) A small reusable view — keep them under ~60 lines
struct PriceTag: View {
let cents: Int
var currency: String = "AUD"
private var formatted: String {
let f = NumberFormatter()
f.numberStyle = .currency
f.currencyCode = currency
return f.string(from: NSNumber(value: Double(cents) / 100)) ?? ""
}
var body: some View {
Text(formatted)
.font(.callout.monospacedDigit())
.foregroundStyle(.secondary)
.accessibilityLabel(Text("Price \(formatted)"))
}
}
// 2) Modifier order matters. Padding before background ≠ background before padding.
struct Card<Content: View>: View {
@ViewBuilder var content: () -> Content
var body: some View {
content()
.padding(16)
.background(.background.secondary, in: RoundedRectangle(cornerRadius: 12))
.overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(.separator))
.shadow(radius: 1, y: 1)
}
}
// 3) Build screens by stacking and conditional rendering
struct ProductRow: View {
let name: String
let priceCents: Int
let onSale: Bool
var body: some View {
HStack(spacing: 12) {
Image(systemName: "bag")
.frame(width: 32, height: 32)
.background(.tint.opacity(0.15), in: Circle())
VStack(alignment: .leading, spacing: 2) {
Text(name).font(.body.weight(.medium))
if onSale {
Label("On sale", systemImage: "tag.fill")
.font(.caption2.weight(.semibold))
.foregroundStyle(.red)
}
}
Spacer()
PriceTag(cents: priceCents)
}
.padding(.vertical, 8)
.accessibilityElement(children: .combine)
}
}
// 4) Navigation with NavigationStack — typed routes (Swift 5.9+)
enum Route: Hashable { case productDetail(String) }
struct CatalogView: View {
let products = [
(id: "sku-1", name: "Wool jacket", priceCents: 19_900, onSale: true),
(id: "sku-2", name: "Linen shirt", priceCents: 8_900, onSale: false),
]
var body: some View {
NavigationStack {
List(products, id: \.id) { p in
NavigationLink(value: Route.productDetail(p.id)) {
ProductRow(name: p.name, priceCents: p.priceCents, onSale: p.onSale)
}
}
.listStyle(.insetGrouped)
.navigationTitle("Catalog")
.navigationDestination(for: Route.self) { route in
switch route {
case .productDetail(let id):
Text("Detail for \(id)").padding()
}
}
}
}
}
// 5) Adapt to dark mode, dynamic type, RTL — all free via the system styling
struct Demo: View {
var body: some View {
Card {
VStack(alignment: .leading) {
Text("Heading").font(.title3.weight(.semibold))
Text("Body uses semantic colours").foregroundStyle(.secondary)
}
}
.padding()
.preferredColorScheme(.dark) // for preview
}
}
#Preview { Demo() }
Why it matters
Lean on semantic colours (.secondary, .tertiary, .background, Tint), system fonts (.title, .body), and SF Symbols. They reflow for dynamic type, recolour for dark mode, and translate for accessibility — all without a single override. Custom colours and fonts are correct for the one screen they brand and wrong for every screen that should follow the user setting.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
Text("Hello")
.font(.title)
.foregroundStyle(.blue)
.padding()
.background(.yellow)
Try it Yourself »
Discussion
Loading…