I currently cannot test the examples yet but I read about some very interesting and highly inconvenient drawback. I think people will run into this issue million of times. On top of that it's very easy to miss and probably hard to debug and drill down to.
So the documentation has the following example and explanation:
A view forms a dependency when its body reads an observable property directly. However, a dependency isn’t formed when a content closure in body reads the property. For example, the following code reads the Book property title inside the content closure of a List. The view’s body doesn’t read this property directly, which means the view won’t update when a book’s title changes.
struct LibraryView: View {
var books: [Book]
var body: some View {
List(books) { book in
Text(book.title)
}
}
}
To ensure that the display of individual list items update when data changes, refactor the content closure to use a custom view for each list item. For example, the following code displays the book’s title in BookView, which reads the property directly in its body:
struct LibraryView: View {
var books: [Book]
var body: some View {
List(books) { book in
BookView(book: book)
}
}
}
struct BookView: View {
var book: Book
var body: some View {
Text(book.title)
}
}
The WWDC session by @Philippe_Hausler says at around 7:40 something totally different.
"Because SwiftUI tracks access to fields per instance, it means that you can use arrays, optionals, or for that matter, any type that contains your observable models. The donut list view has an array of donut models. Each model itself is '@Observable'. When any of the names of those donuts change, SwiftUI detects the access to that property on that specific instance and tracks it to know when to invalidate the view. So here, when the donut name is changed via the randomize button, the view updates accordingly. This lets you build your models how you want. You can have arrays of models being observed, or even model types that contain other observable model types. The general rule is for Observable, if a property that is used changes, the view will update."
@Observable class Donut {
var name: String
}
struct DonutList: View {
var donuts: [Donut]
var body: some View {
List(donuts) { donut in
HStack {
Text(donut.name)
Spacer()
Button("Randomize") {
donut.name = randomName()
}
}
}
}
}
Highly likely, this is very fast moving stuff; remember we had some major discussions / changes to observability on this forum just a couple of weeks before WWDC.
PS. makes me grin every time I hear them saying "this platform" as a placeholder for visionOS Apparently the name wasn't finalised at the time of the recording.
Fitting code on slides sometimes requires some things to be elided.
The initializer part is something I think is reasonable to contain within the final version (because it is pretty heinous not to have it and rely on the default value definite initialization). Hence why I am a very strong +1 on the init accessors pitch. To me it is clear we need a solution in that space.
The docs should probably be updated. Since that will (with modifications to account for the initializers and identifiable parts) work today.
Totally understandable and wasn't any complain in the first place.
Great, so it's an outdated doc issue here then.
So if I understand this correctly, the @Observable macro would inject an init accessor to the property in question so that when assigned to through the observable type's init it will properly initialize the backing storage.
Without the concrete specifics from the proposal, the mental model is something like this:
@Observable
class Donut: Identifiable {
var name: String {
init { /* init backing store for name */ }
...
}
init(name: String) {
self.name = name // goes through `name.init`
}
}