private struct WidthPreferenceKey: PreferenceKey {
struct Holder: Equatable { // .onPreferenceChange(key:perform:) wants this to be Equatable
let which: Binding<CGFloat?>
let value: CGFloat
static func == (lhs: Self, rhs: Self) -> Bool {
/* how to compare 'which'? */
lhs.value == rhs.value
}
}
typealias Value = Holder
static var defaultValue: Value = Holder(which: .constant(nil), value: 0)
static func reduce(value: inout Value, nextValue: () -> Value) {
value = nextValue()
}
}
The problem is I don't know how to implement Equatable for WidthPreferenceKey.Holder because I don't know how to test for Binding<CGFloat?> equality. It's equal if it's "pointing" to the same "source" which is from a @State variable's $stateVarName.
You can conform it to Equatable defensively (like you just did), since the PreferenceKey.Value needs to conform to equatable only so that it can skip updating the view in case PreferenceKey.Value doesn't change. This would also mean there will be no transition animation if == evaluates to true.
So if there are cases that you need to update PreferenceValue but doesn't want SwiftUI to trigger update, you need to return true.
Be careful with this notion. SwiftUI does not have any concept of reference, only the position in the hierarchy. Because you can do things like this:
struct Foo: View {
@State private var state: String = ""
var body: some View {
VStack { Text(state); TextField("Test", text: $state) }
}
}
struct Bar: View {
var body: some View {
let foo = Foo()
return VStack { foo; foo }
}
}
Which does look weird, since you're using the samefoo but get different state, but would make sense if you think about this in terms of hierarchical position.
If you want to figure out where in the hierarchy does Binding come from, you'll need to attach that information to Holder and compare them (remember that Binding can come from custom closure as well). It is a little tricky and I think it'd be overkill since defensive equality seems to be working in you case.