- The property wrapper type must have a property named
value
I've been playing with property wrappers and re-implementing @Binding
and @State
from SwiftUI, and just realised that the name value
might not be the best choice here when used in conjunction with @dynamicMemberLookup
like it is in the context of SwiftUI
Especially, in SwiftUI @Binding
makes use of @dynamicMemberLookup
to easily map from a Binding to another Binding to an inner property:
let personBinding: Binding<Person> = ...
let nameBinding: Binding<String> = personBinding.name
The problem is that if we have a model object with a property exactly named value
, the behavior will be different for this value
property as opposed to all other properties, because .value
is a property already existing on Binding
so it won't go thru the subscript(dynamicMember:)
like all others.
This is normal behavior and by design of @dynamicMemberLookup
, but in the context using Property Wrappers with DML (like SwiftUI heavily does), given that value
seems like a pretty common name for a property, we are quite likely to clash and hit this inconsistency in behavior, which could quickly become confusing – especially to new users of SwiftUI who don't really know the implementation details of @State
and @Binding
For example, suppose we have these definitions:
struct CartItem {
var name: String
var value: Double
}
struct CartCell: View {
@State var item: CartItem
var body: some View {
HStack {
Text(item.name)
Text("\(item.value)")
}
}
}
Then it's easy to encounter the confusing behavior here:
let cell = CartCell(item: CartItem(name: "Mac Pro", value: 5999.00))
let itemBinding: Binding<CartItem> = cell.$item // OK
let nameBinding: Binding<String> = cell.$item.name // OK
let valueBinding: Binding<Double> = cell.$item.value // cannot convert value of type 'CartItem' to specified type 'Binding<Double>'
Here cell.$item.value
will access the Binding<CartItem>.value
here, thus returning the CartItem
pointed by the property wrapper's value, as opposed to transforming the Binding<CartItem>
into a Binding<Double>
similar to how it did for name
.
By design of @dynamicMemberLookup
, the problem will always exist whatever name we chose for the value
and wrapperValue
properties, but value
seems like a quite common name for a property making it way too likely imho to run into this inconsistency.
I propose we thus find a less common name for the value
property in this proposal. My suggestion would be propertyWrapperValue
instead of just value
, and propertyWrapper
instead of wrapperValue
– making those names way less likely to be encountered in a custom Model object of the user's code**.