import Observation import Portico /// Demonstrates `@Environment` + `@Observable`: one model injected into a /// subtree, read by multiple different view types, with mutations propagating /// through the Observation bridge and two-way bindings writing back into the /// model with no extra plumbing. /// /// The model is declared at file scope so every pane resolves the same type /// from `@Environment(DemoModel.self)`. `.environment(model)` scopes it to the /// `VStack` that holds both panes. // MARK: - Model @Observable private final class DemoModel { var label = "Hello, Environment!" var isDark = false var fontSize = 14.0 } // MARK: - Panes /// Read-only status pane: displays the model's current state. Any mutation from /// the controls pane reaches this widget through the Observation bridge — no /// `Binding`, no manual subscription. private struct StatusPane: View { @Environment(DemoModel.self) private var model var body: some View { VStack { @Bindable var model = model VStack(spacing: 8) { Label($model.label) .title2() Label("Font: \(Int(model.fontSize))pt, \(model.isDark ? "Dark" : "Light")") .dimmed() } .margin(16) } .card() .halign(.center) } } /// Interactive controls pane: mutates the injected model through two-way /// bindings (`Binding(model, \.keyPath)`). Every change writes directly into /// the model; the status pane sees it on the next idle flush. private struct ControlsPane: View { @Environment(DemoModel.self) private var model var body: some View { ListBox { @Bindable var model = model EntryRow() .title("Label Text") .text($model.label) SwitchRow() .title("Dark Mode") .active($model.isDark) SpinRow(min: 8, max: 48, step: 2) .title("Font Size") .value($model.fontSize) } .boxedList() } } // MARK: - Page struct EnvironmentDemoPage: View { private let model = DemoModel() var body: some View { Clamp { StatusPage { VStack(spacing: 24) { StatusPane() ControlsPane() } .environment(model) } .title("Environment + Observable") .description( "An @Observable model injected with .environment(model), " + "read by two view types. Controls write through " + "Binding(model, \\.keyPath); the bridge pushes updates " + "to every widget that read the changed property." ) .iconName("network-workgroup-symbolic") } .hexpand(true) .vexpand(true) } }