@Observable does not conform with Equatable (and Hashable)

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.

Post not yet marked as solved Up vote post of jcfmunoz Down vote post of jcfmunoz
1.1k views
  • Thanks for filing FB12535713.

Add a Comment

Replies

Xcode 15 beta 7 and there's no fix for this. @Observable can conform to Identifiable protocol, but not with Equatable (and Hashable).

Because of this I cannot use .navigationDestination API, but perhaps more important than that: if I want to inject into a master/detail instance of a data from my app, I have to do it as a let and load in an .onAppear its values on other @State that allow me to edit its values. If @Observable conformed to Hashable I could pass the instance of the selected data without problem and use a @Bindable to edit it directly on the instance itself. That would be perfect.

Thanks in advance, Apple staff

It's simple to provide conformance using ObjectIdentifier, e.g.

import SwiftUI

@Observable class ObservableContent: Hashable {
    var text1 = "Default"
    var text2 = ""

    static func == (lhs: ObservableContent, rhs: ObservableContent) -> Bool {
        lhs === rhs
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }
}

struct ContentView: View {
    @State var observableContent: ObservableContent?
        
    var body: some View {
        Group {
            if let observableContent {
                NavigationStack {
                    NavigationLink(value: observableContent) {
                        Text("Navigation Link")
                    }
                    .navigationDestination(for: ObservableContent.self) { content in
                        ObservableContentView(content: content)
                    }
                }
            }
        }
        .onAppear {
            if observableContent == nil {
                observableContent = ObservableContent()
            }
        }
        .onDisappear {
            observableContent = nil
        }
    }
}

struct ObservableContentView: View {
    @Bindable var content: ObservableContent
    
    var body: some View {
        Form {
            TextField("Text1", text: $content.text1)
            Text(content.text1)
        }
    }
}