Since Xcode 15 beta 5, making a class with the @Observable macro no longer requires all properties to have an initialization value, as seen in the video. Just put an init that collects the properties and everything works correctly.
@Observable
final class Score: Identifiable {
let id: Int
var title: String
var composer: String
var year: Int
var length: Int
var cover: String
var tracks: [String]
init(id: Int, title: String, composer: String, year: Int, length: Int, cover: String, tracks: [String]) {
self.id = id
self.title = title
self.composer = composer
self.year = year
self.length = length
self.cover = cover
self.tracks = tracks
}
}
But there is a problem: the @Observable macro makes each property to integrate the @ObservationTracked macro that seems not to conform the types to Equatable, and in addition, to Hashable.
Obviously, being a feature of each property, it is not useful to conform the class in a forced way with the static func == or with the hash(into:Hasher) function that conforms both protocols.
That any class we want to be @Observable does not conform to Hashable, prevents any instance with the new pattern to be usable within a NavigationStack using the data driven navigation bindings and the navigationDestination(for:) modifier.
I understand that no one has found a solution to this. If you have found it it would be great if you could share it but mainly I am making this post to invoke the mighty developers at Apple to fix this bug. Thank you very much.
P.S. - I also posted a Feedback (FB12535713), but no one replies. At least that I see.
Discover Observation in SwiftUI
RSS for tagDiscuss the WWDC23 Session Discover Observation in SwiftUI
Posts under wwdc2023-10149 tag
4 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hi. The binding in a ForEach or List view doesn't work anymore when using the @Observable macro to create the observable object. For example, the following are the modifications I introduced to the Apple's example called "Migrating from the Observable Object Protocol to the Observable Macro" https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro
struct LibraryView: View {
@Environment(Library.self) private var library
var body: some View {
List($library.books) { $book in
BookView(book: book)
}
}
}
All I did was to add the $ to turn the reference to library.books into a binding but I got the error "Cannot find '$library' in scope"
Is this a bug or the procedure to use binding in lists changed?
Thanks
Previously, it was recommended to use the @MainActor annotation for ObservableObject implementation.
@MainActor
final class MyModel: ObservableObject {
let session: URLSession
@Published var someText = ""
init(session: URLSession) {
self.session = session
}
}
We could use this as either a @StateObject or @ObservedObject:
struct MyView: View {
@StateObject let model = MyModel(session: .shared)
}
By moving to Observation, I need to the @Observable macro, remove the @Published property wrappers and Switch @StateObject to @State:
@MainActor
@Observable
final class MyModel {
let session: URLSession
var someText = ""
init(session: URLSession) {
self.session = session
}
}
But switching from @StateObject to @State triggers me an error due to a call to main-actor isolated initialiser in a synchronous nonisolated context.
This was not the case with @StateObject of @ObservedObject.
To suppress the warning I could :
mark the initializer as nonisolated but it is not actually what I want
Mark the View with @MainActor but this sounds odd
Both solutions does not sound nice to my eye.
Did I miss something here?
The code for @State doesn't seem to work.
struct DonutListView: View {
var donutList: DonutList
@State private var donutToAdd: Donut?
var body: some View {
List(donutList.donuts) { DonutView(donut: $0) }
Button("Add Donut") { donutToAdd = Donut() }
.sheet(item: $donutToAdd) { // <-- would need a "donut in"
TextField("Name", text: $donutToAdd.name) // <-- donutToAdd is optional and I'm not sure how it would be unwrapped
Button("Save") {
donutList.donuts.append(donutToAdd)
donutToAdd = nil
}
Button("Cancel") { donutToAdd = nil }
}
}
}
Does anyone have a fix for this?
Thanks,
Dan!