Why I Can Not Use Equatable Function in My View When the View Can Use It in Swiftui

EquatableView not forcing SwiftUI to use custom implementation of the == function

Equatable is used when a view is created, so either it should replace already existed or not. In your example TestView is not re-created, because it should not on binding changed - only body rendered with new value.

To simulate recreation (for testing purpose) the simplest it to add condition. Equatibility is just a optimisation technic it helps SwiftUI to detect replace existed view instance or not.

Here is updated example to demo Equatable works. Tested with Xcode 12.1 / iOS 14.1

struct ContentView: View {
@State var state = false
var body: some View {
VStack {
if state {
EquatableView(content: TestView(state: $state))
}
Button("Change state", action: {state.toggle()})
}
}
}

struct TestView: View, Equatable {
@Binding var state: Bool
init(state: Binding<Bool>) {
_state = state
print(">> inited") // << check if created
}
var body: some View {
let _ = print("Test updated")
Text("TestView state : \(state.description)")
}
static func == (lhs: TestView, rhs: TestView) -> Bool {
//Never printed or invoked
let _ = print ("lhs == rhs invoked \(lhs.state) == \(rhs.state)")
return lhs.state == rhs.state
}
}

SwiftUI does not reliably propagate changes to child view

This is a result of your custom Equatable conformance:

extension Book: Equatable {
static func == (lhs: Book, rhs: Book) -> Bool {
return lhs.id == rhs.id
}
}

If you remove this, it'll work as expected.

In your current code, you're saying that if two Books have the same ID, they are equal. My suspicion is that you don't actually mean that they are truly equal -- you just mean that they are the same book.

Your second option ("add && lhs.likes == rhs.likes to the Equatable conformance (which is not intended)") essentially just uses the synthesized Equatable conformance that the system generates, since unusedProperty isn't used -- so, if you were to use the second option, you may as well just remove the custom == function altogether.

I think the decision to make here is whether you really want to tell the system an untruth (that two Books, no matter what their other properties, are equal if they share the same id) or if you should let the system do it's own work telling if the items are equal or not.

Observable object model not changing View values when model is updated

The main reason the card view doesn't see changes is because in your card view you did put an equatable conformance protocol where you specify an equality check == function that just checks for content and not other variable changes

static func ==(lhs: Game.Card, rhs: Game.Card) -> Bool {
lhs.content == rhs.content
// && lhs.isFaceUp && rhs.isFaceUp //<- you can still add this
}

if you remove the equatable protocol and leave swift to check for equality it should be the minimal change from your base solution.

I would still use the solution where you change the state of the class card so the view can react to changes as an ObservableObject, and the @Published for changes that the view need to track, like this:

class Card: Identifiable, Equatable, ObservableObject {
var id: Int
@Published var isFaceUp: Bool = false
var content: String
@Published var isMatchedUp: Bool = false
var isPreviouslySeen = false

internal init(id: Int, content: String) {
self.id = id
self.content = content
}

static func ==(lhs: Game.Card, rhs: Game.Card) -> Bool {
lhs.content == rhs.content
}
}

and in the Card view the card variable will become

struct Card: View {
@ObservedObject var card: Game.Card
...
}

btw you don't need to notify the view of changes with
objectWillChange.send() if you are already using the @Published notation. every set to the variable will trigger an update.



Related Topics



Leave a reply



Submit