iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

SwiftUI Intro

SwiftUI is Apples declarative UI framework: views are structs, state lives in @State / @Observable, and changes trigger a diffed re-render. It targets iOS, iPadOS, macOS, watchOS, tvOS, and visionOS with the same code. The mental model is React-flavoured, with stronger compiler guarantees because everything is statically typed.

A SwiftUI screen with state, navigation, and a list

EXAMPLE
import SwiftUI

// 1) Observable model (Swift 5.9+) — emit changes to any view that reads it
@Observable
final class CartModel {
    var items: [Item] = []
    var lastError: String?

    func add(_ item: Item) { items.append(item) }
    func remove(_ item: Item) { items.removeAll { $0.id == item.id } }
    var totalCents: Int { items.reduce(0) { $0 + $1.priceCents } }
}

struct Item: Identifiable, Hashable {
    let id = UUID()
    let name: String
    let priceCents: Int
}

// 2) Root view — passes the model through the environment
@main
struct ShopApp: App {
    @State private var cart = CartModel()
    var body: some Scene {
        WindowGroup {
            NavigationStack {
                CatalogView()
            }
            .environment(cart)
        }
    }
}

// 3) Catalog with a list and per-row actions
struct CatalogView: View {
    @Environment(CartModel.self) private var cart
    @State private var query = ""

    private let catalog = [
        Item(name: "Wool Jacket", priceCents: 19900),
        Item(name: "Linen Shirt", priceCents: 8900),
        Item(name: "Beanie",      priceCents: 2900),
    ]

    private var filtered: [Item] {
        guard !query.isEmpty else { return catalog }
        return catalog.filter { $0.name.localizedCaseInsensitiveContains(query) }
    }

    var body: some View {
        List {
            ForEach(filtered) { item in
                HStack {
                    Text(item.name)
                    Spacer()
                    Text(price(item.priceCents)).monospacedDigit()
                    Button("Add") { cart.add(item) }.buttonStyle(.bordered)
                }
            }
        }
        .searchable(text: $query)
        .navigationTitle("Catalog")
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                NavigationLink {
                    CartView()
                } label: {
                    Label("Cart (\(cart.items.count))", systemImage: "cart")
                }
            }
        }
    }
}

// 4) Cart view — sheet, swipe-to-delete, sum
struct CartView: View {
    @Environment(CartModel.self) private var cart
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        List {
            Section("Items") {
                ForEach(cart.items) { item in
                    HStack {
                        Text(item.name)
                        Spacer()
                        Text(price(item.priceCents))
                    }
                    .swipeActions(edge: .trailing) {
                        Button(role: .destructive) { cart.remove(item) }
                            label: { Label("Remove", systemImage: "trash") }
                    }
                }
            }
            Section("Summary") {
                LabeledContent("Total", value: price(cart.totalCents))
                    .font(.title3.weight(.semibold))
            }
        }
        .navigationTitle("Cart")
    }
}

private func price(_ cents: Int) -> String {
    let f = NumberFormatter(); f.numberStyle = .currency; f.currencyCode = "AUD"
    return f.string(from: NSNumber(value: Double(cents) / 100)) ?? ""
}

Why it matters

Reach for @Observable + Environment over @ObservableObject + @StateObject — the new macro-based observation system (Swift 5.9+) is finer-grained, only re-renders the views that read the changed property, and removes most of the @Published / @ObservedObject ceremony.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import SwiftUI
struct ContentView: View {
    @State private var n = 0
    var body: some View {
        VStack {
            Text("Count: \(n)")
            Button("Tap") { n += 1 }
        }
        .padding()
    }
}
Try it Yourself »

Exercise

Declarative view return type.

var body: View { Text("Hi") }

Discussion

Loading…