How to get Binding via dollar prefix in SwiftUI

Issue #488

The dollar is not a prefix, it seems to be a generated code for property wrapper, and each kind of property wrapper can determine which value it return via this dollar sign

State and ObservedObject are popular property wrappers in SwiftUI

State

Read State

A persistent value of a given type, through which a view reads and monitors the value.

If we have a simple State, we can access its 3 forms

1
@State private var image: UIImage

and here is what

1
2
3
image // UIImage
_image // State<UIImage>
$image // Binding<UIImage>

Also, with State, we can access Binding via projectedValue

1
_image.projectedValue // Binding<UIImage>

ObservableObject

Read ObservedObject

For a simple ObservableObject, we can see its 3 forms

1
2
3
4
5
6
7
8
9
class ViewModel: ObservableObject {
var objectWillChange = ObservableObjectPublisher()

var image: UIImage? {
didSet {
objectWillChange.send()
}
}
}
1
@ObservedObject var viewModel: ViewModel = ViewModel()
1
2
3
viewModel // ViewModel
_viewModel // ObservedObject<ViewModel>
$viewModel // ObservedObject<ViewModel>.Wrapper

If we view the source code of ObservableObject, we can see its Wrapper which uses dynamicMemberLookup to provide Binding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@propertyWrapper public struct ObservedObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject {

/// A wrapper of the underlying `ObservableObject` that can create
/// `Binding`s to its properties using dynamic member lookup.
@dynamicMemberLookup public struct Wrapper {

/// Creates a `Binding` to a value semantic property of a
/// reference type.
///
/// If `Value` is not value semantic, the updating behavior for
/// any views that make use of the resulting `Binding` is
/// unspecified.
public subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>) -> Binding<Subject> { get }
}

public init(initialValue: ObjectType)

public init(wrappedValue: ObjectType)

public var wrappedValue: ObjectType

public var projectedValue: ObservedObject<ObjectType>.Wrapper { get }
}

Derived State

If we have a struct with @State in a SwiftUI view, we can access its property as Binding. This derived state mechanism is done via Dynamic keypath member lookup feature of Swift 5.1

1
2
3
4
5
6
7
struct ViewModel {
var image: UIImage?
}

struct MainView: View {
@State private var viewModel: ViewModel = ViewModel()
}

Take a look at the interface of Binding

1
2
3
4
5
6
/// A value and a means to mutate it.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper @dynamicMemberLookup public struct Binding<Value> {
/// Creates a new `Binding` focused on `Subject` using a key path.
public subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Value, Subject>) -> Binding<Subject> { get }
}

So if use $viewModel.image we can access its property as Binding

1
2
3
viewModel.image // UIImage?
$viewModel.image // Binding<UIImage?>
$viewModel.$image // Value of type 'ViewModel' has no member '$image'

Conclusion

So now we know how to get Binding from State and ObservableObject, and the mysterious dollar sign. These are both convenient but confusing at first, but if we use it more, it will make more sense and hopefully we can learn to do the same for our own property wrappers

Comments