Skip to content

Instantly share code, notes, and snippets.

@allenhumphreys
Created May 12, 2023 18:37
Show Gist options
  • Save allenhumphreys/5b5d8d601391e25234e7b6bc39a45bec to your computer and use it in GitHub Desktop.
Save allenhumphreys/5b5d8d601391e25234e7b6bc39a45bec to your computer and use it in GitHub Desktop.
Swift 5.9 Observability
import SwiftUI
@available(iOS 9999, *)
@main
struct ObservabilityApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@available(iOS 9999, *)
struct ContentView: View {
let model: Model = Model()
@StateObject var renderer = SwiftUIPretendRenderer()
var body: some View {
renderer.observe {
mainContent
}
}
var mainContent: some View {
Form {
Section("A basic property") {
Text("Updating this property always triggers the new value to be visible")
Button("Update") {
// Normal property, does the normal things
model.property = "\(Int.random(in: 1...1000))"
}
Text(model.property)
}
Section("An ignored property") {
Text("This property is ignored, so changing the value doesn't reflect in the UI until something else triggers a render")
Button("Update") {
// This property is ignored, so changes to it don't trigger a render
model.ignoredProperty = "\(Int.random(in: 1...1000))"
}
Text(model.ignoredProperty)
}
Section("A property that is not read") {
Text(try! AttributedString(markdown: "Because this property is not read within `withObservationTracking`, changing it will never trigger a render. This is similar to if the property is ignored."))
Button("Update") {
// This property is not read, therefore changing it doesn't trigger a render
model.nonAccessedProperty = "\(Int.random(in: 1...1000))"
}
}
Section("A text binding") {
Text(try! AttributedString(markdown: "A normal model-backed `String` for a `TextField` `Binding`"))
TextField(
"Enter Some Words",
// I wonder if we'll see a convenience for creating Binding which is currently
// the responsibility of StateObject and ObservedObject
text: Binding(
get: {
model.textFieldValue
},
set: {
model.textFieldValue = $0
}
)
)
.textFieldStyle(.roundedBorder)
Text("You entered: \(model.textFieldValue.isEmpty ? "Nothing yet" : model.textFieldValue)")
}
}
}
}
@available(iOS 9999, *)
@Observable public final class Model {
var property: String = "Start"
@ObservationIgnored var ignoredProperty: String = "Start"
var nonAccessedProperty: String = "Start"
var textFieldValue: String = ""
}
  1. You need to development snapshot from swift.org
  2. After it is installed, you need to activate it on your project via the menu Xcode > Toolchains
  3. You need to add an OTHER_SWIFT_FLAGS setting on the target/project you can paste this into the Xcode UI: -Xfrontend -load-plugin-library -Xfrontend ${TOOLCHAIN_DIR}/usr/lib/swift/host/plugins/libObservationMacros.dylib
@available(iOS 9999, *)
final class SwiftUIPretendRenderer: ObservableObject {
@MainActor
@Published
var reRenderPlease: Void = ()
@Sendable
func scheduleRender() {
Task { @MainActor in
reRenderPlease = ()
}
}
func observe<T>(_ work: () -> T) -> T {
// I think SwiftUI will effectively use this function to implicitly watch for changes
return withObservationTracking(
work,
onChange: scheduleRender
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment