|
| 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. |
0 commit comments