Skip to content

Commit 6ae1135

Browse files
committed
Add observation example
1 parent 305fc26 commit 6ae1135

File tree

4 files changed

+144
-0
lines changed

4 files changed

+144
-0
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Created by Luke Zhao on 10/29/25.
2+
3+
import UIKit
4+
import UIComponent
5+
6+
/// In iOS 26, UIViewController has built-in support for observing @Observable objects.
7+
/// Just override updateProperties() to update your component when any observed property changes.
8+
@available(iOS 26.0, *)
9+
class ObservableViewController: UIViewController {
10+
@Observable
11+
private class ViewModel {
12+
var count: Int = 0
13+
}
14+
15+
private let viewModel = ViewModel()
16+
17+
override func updateProperties() {
18+
super.updateProperties()
19+
let viewModel = viewModel
20+
view.componentEngine.component = VStack(spacing: 8, justifyContent: .center, alignItems: .center) {
21+
Text("Hello world!", font: .boldSystemFont(ofSize: 22))
22+
Text("Count: \(viewModel.count)")
23+
Text("Increase").tappableView {
24+
viewModel.count += 1
25+
}
26+
}.fill()
27+
}
28+
}
29+
30+
/// Same can be done in UIView subclasses by overriding updateProperties().
31+
/// Here is an example UIView that uses @Observable to update its component.
32+
@available(iOS 26.0, *)
33+
class CountExampleView: ComponentView {
34+
@Observable private class ViewModel {
35+
var count: Int = 0
36+
}
37+
38+
private let viewModel = ViewModel()
39+
40+
override func updateProperties() {
41+
super.updateProperties()
42+
let viewModel = viewModel
43+
component = VStack(spacing: 8, justifyContent: .center, alignItems: .center) {
44+
Text("Hello world!", font: .boldSystemFont(ofSize: 22))
45+
Text("Count: \(viewModel.count)")
46+
Text("Increase").tappableView {
47+
viewModel.count += 1
48+
}
49+
}.fill()
50+
}
51+
}

Examples/UIComponentExample/ViewController.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ class ViewController: ComponentViewController {
1919
ExampleItem(name: "Size Example", viewController: SizeExampleViewController())
2020
ExampleItem(name: "SwiftUI Example", viewController: SwiftUIExampleViewController())
2121
ExampleItem(name: "Theme System Example", viewController: ThemeSystemViewController())
22+
if #available(iOS 26.0, *) {
23+
ExampleItem(name: "Observable Example", viewController: ObservableViewController())
24+
}
2225
} separator: {
2326
Separator()
2427
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Observable Support in UIKit
2+
3+
Built-in observation for `UIViewController` and `UIView` integrations.
4+
5+
Starting in iOS 26, UIComponent plays nicely with the new Swift Observation system.
6+
``UIViewController`` and ``ComponentView`` automatically track any `@Observable`
7+
state you access inside ``UIViewController/updateProperties()`` or ``ComponentView/updateProperties()``
8+
and re-run those methods whenever the data changes. This lets you treat UIKit surfaces the
9+
same way you would a SwiftUI view: declare the UI from your latest model, and UIComponent
10+
takes care of the rest.
11+
12+
## Observing inside ``UIViewController``
13+
14+
Override ``UIViewController/updateProperties()`` and build your component tree from
15+
observed model data. When any observed property changes, UIComponent renders the
16+
new component automatically.
17+
18+
```swift
19+
@available(iOS 26.0, *)
20+
final class ObservableViewController: UIViewController {
21+
@Observable
22+
private final class ViewModel {
23+
var count: Int = 0
24+
}
25+
26+
private let viewModel = ViewModel()
27+
28+
override func updateProperties() {
29+
super.updateProperties()
30+
let viewModel = viewModel
31+
32+
view.componentEngine.component = VStack(spacing: 8, justifyContent: .center, alignItems: .center) {
33+
Text("Hello world!", font: .boldSystemFont(ofSize: 22))
34+
Text("Count: \(viewModel.count)")
35+
Text("Increase").tappableView {
36+
viewModel.count += 1
37+
}
38+
}.fill()
39+
}
40+
}
41+
```
42+
43+
Key details:
44+
- Access every observed value inside `updateProperties()` so UIKit knows what to track.
45+
- Keep calling `super.updateProperties()` to preserve UIKit book-keeping.
46+
- Assign directly to ``ComponentEngine/component``; diffing and view reuse happen automatically.
47+
48+
## Observing inside ``UIView``
49+
50+
The same pattern works for any ``ComponentView`` (a ``UIView`` subclass tailored for
51+
UIComponent). Create an `@Observable` model, override ``ComponentView/updateProperties()``,
52+
and assign to the `component` property.
53+
54+
```swift
55+
@available(iOS 26.0, *)
56+
final class CountExampleView: ComponentView {
57+
@Observable
58+
private final class ViewModel {
59+
var count: Int = 0
60+
}
61+
62+
private let viewModel = ViewModel()
63+
64+
override func updateProperties() {
65+
super.updateProperties()
66+
let viewModel = viewModel
67+
68+
component = VStack(spacing: 8, justifyContent: .center, alignItems: .center) {
69+
Text("Hello world!", font: .boldSystemFont(ofSize: 22))
70+
Text("Count: \(viewModel.count)")
71+
Text("Increase").tappableView {
72+
viewModel.count += 1
73+
}
74+
}.fill()
75+
}
76+
}
77+
```
78+
79+
## Tips for adopting Observation
80+
81+
- Scope the observable view model privately inside the view or controller to keep
82+
the observation graph local to that surface.
83+
- Reassign the entire component tree from your latest model instead of mutating
84+
existing UIKit views.
85+
- If you support pre-iOS 26, gate your observation-enabled code with availability
86+
checks and fall back to manual `reloadComponent()` calls on earlier systems.
87+
88+
For a complete, runnable sample, see `ObservableExample.swift` in the Examples
89+
project.

Sources/UIComponent/Documentation.docc/UIComponent.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ In version 5.0, UIComponent can also easily render SwiftUI View alongside UIView
3131
- <doc:PerformanceOptimization>
3232
- <doc:Component>
3333
- <doc:Environment>
34+
- <doc:ObservableSupport>
3435
}

0 commit comments

Comments
 (0)