How to implement Binding<CGFloat?> Equatable?

I need this:

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.

What to do?

Not sure why SwiftUI.View.onPreferenceChange(key:perform:) want K.Value: Equatable, I just simply do:

        static func == (lhs: Self, rhs: Self) -> Bool {
            /* how to compare 'which'? */
            
//            lhs.value == rhs.value
            false.  // or true
        }

It seems work for my use case. But still, I like to know how to properly implement Equatable here...

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 same foo 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.

1 Like
Terms of Service

Privacy Policy

Cookie Policy