Goal: Choose the right SwiftUI model for state: @State, @Binding, @StateObject, @ObservedObject, @EnvironmentObject, and @Published.
import SwiftUI
struct CounterLocal: View {
@State private var count: Int = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") { count += 1 }
}
}
}
struct CounterWithBinding: View {
@State private var count = 0
var body: some View {
VStack {
Text("Parent count: \(count)")
ChildCounter(count: $count)
}
}
}
struct ChildCounter: View {
@Binding var count: Int
var body: some View { Button("+1") { count += 1 } }
}
final class TodosVM: ObservableObject {
@Published var items: [String] = []
@Published var isLoading = false
func load() async {
isLoading = true
defer { isLoading = false }
// Simulate fetch
try? await Task.sleep(nanoseconds: 400_000_000)
items = ["Milk", "Bread", "Eggs"]
}
}
struct TodosScreen: View {
@StateObject private var vm = TodosVM()
var body: some View {
Group {
if vm.isLoading { ProgressView() }
else { List(vm.items, id: \.self, rowContent: Text.init) }
}
.task { await vm.load() }
}
}
import SwiftUI
final class Repo: ObservableObject {
func fetchTodos() async throws -> [String] {
try await Task.sleep(nanoseconds: 300_000_000)
return ["Milk", "Bread", "Eggs"]
}
}
@MainActor
final class AppVM: ObservableObject {
@Published var items: [String] = []
@Published var error: String? = nil
@Published var loading = false
private let repo: Repo
init(repo: Repo) { self.repo = repo }
func load() async {
loading = true
defer { loading = false }
do { items = try await repo.fetchTodos() }
catch { self.error = String(describing: error) }
}
}
struct ContentView: View {
@StateObject private var vm = AppVM(repo: Repo())
var body: some View {
NavigationView {
Group {
if vm.loading { ProgressView() }
else if let e = vm.error { Text(e) }
else { List(vm.items, id: \.self, rowContent: Text.init) }
}
.navigationTitle("Todos")
}
.task { await vm.load() }
}
}
@main
struct AppStateDemo: App {
var body: some Scene {
WindowGroup { ContentView() }
}
}
Notes
TodosVM
, Repo
, and ContentView
into sandboxes/ios-swiftui/skeleton/Todos.swift
or your SwiftUI project.AppStateDemo
(or your app) as the entry point in the project.