100 lines
2.9 KiB
Swift
100 lines
2.9 KiB
Swift
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)
|
|
}
|
|
}
|