This tutorial by Apple about SwiftUI uses a dollar sign to bind data, and I‘m having trouble finding more information about this data binding in SwiftUI.
Toggle(isOn: $showFavoritesOnly) {
You use the $ prefix to access a binding to a state variable, or one of its properties.
Is this some sort of inout type parameter? That uses the ampersand to pass it on.
This is very well explained in WWDC 2019 video 415. You are merely looking at one special case of a broad language feature, namely property wrappers.
A property wrapper (such as #State) is actually a way of referring to an instance of a type (usually a struct or enum) with the same name (such as State). The latter provides instructions for turning this instance property into a computed property whose getter and setter are the getter and setter for a certain computed property of itself (its wrappedValue). It also typically holds private storage backing that computed property.
Thus, after the declaration
#State var showFavoritesOnly = true
...showFavoritesOnly is turned into a computed property, with its getter and setter supplied by the State struct. When you set showFavoritesOnly to true, that is routed through the State struct's setter and ends up in a stored property of the State instance.
All of this implies that somewhere there is a State instance associated with your showFavoritesOnly. And there is, but it's hidden from view. Its name, in case you'd like to see that State instance, is _showFavoritesOnly.
Okay, but when you say $showFavoritesOnly, you do not get a State struct; you get a Binding struct. Why? That's because a property wrapper has a mechanism for specifying what the returned value from the $ name should be. In the case of State, it specifies that this value should be its own binding property, which is a Binding (see the docs: https://developer.apple.com/documentation/swiftui/state).
By an amazing coincidence, Toggle's isOn initializer takes a Binding (again, see the docs, https://developer.apple.com/documentation/swiftui/toggle/3232112-init). You could not have set the Toggle's isOn to showFavoritesOnly even if you wanted to! Instead, you set it to the Binding<Bool> supplied by the State instance, so that the Toggle has automatic two-way communication with the State object. The SwiftUI framework enforces its own correct usage; a Toggle can exist only in relation to some binding that acts as the underlying source of truth for its on/off state. And because it's a binding, not a mere Bool, communication works in both directions: when the user taps the switch in the Toggle, the change in value flows "up" to the State variable by way of the binding.
The $ is used in conjunction with property wrappers (previously known as "property delegates").
It's not an operator, but a prefix (thanks #matt!).
For more about property delegates, see this Swift Evolution document.
e.g. in #State var aState = false, State is a property wrapper.
This means that if we write:
aState we're accessing a Bool value
$aState we're accessing a Binding<Bool> value
Different property delegates will generate different values, called "projected values".
Related
This tutorial by Apple about SwiftUI uses a dollar sign to bind data, and I‘m having trouble finding more information about this data binding in SwiftUI.
Toggle(isOn: $showFavoritesOnly) {
You use the $ prefix to access a binding to a state variable, or one of its properties.
Is this some sort of inout type parameter? That uses the ampersand to pass it on.
This is very well explained in WWDC 2019 video 415. You are merely looking at one special case of a broad language feature, namely property wrappers.
A property wrapper (such as #State) is actually a way of referring to an instance of a type (usually a struct or enum) with the same name (such as State). The latter provides instructions for turning this instance property into a computed property whose getter and setter are the getter and setter for a certain computed property of itself (its wrappedValue). It also typically holds private storage backing that computed property.
Thus, after the declaration
#State var showFavoritesOnly = true
...showFavoritesOnly is turned into a computed property, with its getter and setter supplied by the State struct. When you set showFavoritesOnly to true, that is routed through the State struct's setter and ends up in a stored property of the State instance.
All of this implies that somewhere there is a State instance associated with your showFavoritesOnly. And there is, but it's hidden from view. Its name, in case you'd like to see that State instance, is _showFavoritesOnly.
Okay, but when you say $showFavoritesOnly, you do not get a State struct; you get a Binding struct. Why? That's because a property wrapper has a mechanism for specifying what the returned value from the $ name should be. In the case of State, it specifies that this value should be its own binding property, which is a Binding (see the docs: https://developer.apple.com/documentation/swiftui/state).
By an amazing coincidence, Toggle's isOn initializer takes a Binding (again, see the docs, https://developer.apple.com/documentation/swiftui/toggle/3232112-init). You could not have set the Toggle's isOn to showFavoritesOnly even if you wanted to! Instead, you set it to the Binding<Bool> supplied by the State instance, so that the Toggle has automatic two-way communication with the State object. The SwiftUI framework enforces its own correct usage; a Toggle can exist only in relation to some binding that acts as the underlying source of truth for its on/off state. And because it's a binding, not a mere Bool, communication works in both directions: when the user taps the switch in the Toggle, the change in value flows "up" to the State variable by way of the binding.
The $ is used in conjunction with property wrappers (previously known as "property delegates").
It's not an operator, but a prefix (thanks #matt!).
For more about property delegates, see this Swift Evolution document.
e.g. in #State var aState = false, State is a property wrapper.
This means that if we write:
aState we're accessing a Bool value
$aState we're accessing a Binding<Bool> value
Different property delegates will generate different values, called "projected values".
I noticed via an Xcode autocompletion suggestion that #State seems to not only autogenerate a $-prefixed member for accessing the corresponding Binding (as is commonly known), but also a _-prefixed member, seemingly exposing the actual State wrapper.
This makes me wonder, what's the use case for it, and where is it mentioned in the docs?
I found out it's actually due to how Swift (rather than SwiftUI) compiles propertyWrappers under the hood.
From the official swift docs (under propertyWrapper):
The compiler synthesizes storage for the instance of the wrapper type by prefixing the name of the wrapped property with an underscore (_)—for example, the wrapper for someProperty is stored as _someProperty. The synthesized storage for the wrapper has an access control level of private.
Here's what's happening each time you use a propertyWrapper:
(From better programming)
As to its practical application in the context of SwiftUI, you can use it to initialize the #State variable, as described in this SO answer or this blog post.
What does the underscore mean before momentDate? Why is it needed?
The underscored variable name refers to the underlying storage for the Binding struct. This is part of a language feature called Property Wrappers.
Given one variable declaration, #Binding var momentDate: Date, you can access three variables:
self._momentDate is the Binding<Date> struct itself.
self.momentDate, equivalent to self._momentDate.wrappedValue, is a Date. You would use this when rendering the date in the view's body.
self.$momentDate, equivalent to self._momentDate.projectedValue, is also the Binding<Date>. You would pass this down to child views if they need to be able to change the date.
For Binding, the "projected value" ($) is just self, and the difference between _ and $ is only in the access level. However, other property wrappers may project a different type of value (see the #SmallNumber example in the language guide).
I recently ran into an issue where I had to init an #State variable in my init method. This post helped me figure that out.
What I realized was that this is the way to do it:
#State var fullText: String // No default value of ""
init(letter: String) {
_fullText = State(initialValue: list[letter]!)
}
I understand that #State is a property wrapper, and I read the documentation on property wrappers. And from other reading I discovered under the hood this code:
#State private var flag = false
is translated into this code:
private var _flag: State<Bool> = State(initialValue: false)
private var $flag: Binding<Bool> { return _flag.projectedValue }
private var flag: Bool {
get { return _flag.wrappedValue }
nonmutating set { _flag.wrappedValue = newValue }
}
My question is where is it documented that _variableName is created from the wrapped property method and I can redefine it? How is a SwiftUI developer supposed to know that from Apple's docs? I'm trying to find what I'm assuming is Documentation I'm missing.
As property wrappers were a Swift Evolution proposal and were developed as part of Swift's open source philosophy, we can find the best documentation directly in the Proposal SE-0258 document.
This describes the rationale behind the #propertyWrapper design, and the exact specification to how they are implemented (including the definitions of wrapped values and projected values).
Sections that may help in your understanding:
§ A property wrapper type provides the storage for a property that uses it as a wrapper. The wrappedValue property of the wrapper type provides the actual implementation of the wrapper, while the (optional) init(wrappedValue:) enables initialization of the storage from a value of the property's type.
The use of the prefix _ for the synthesized storage property name is deliberate: it provides a predictable name for the synthesized storage property that fits established conventions for private stored properties.
[...]
§ A property wrapper type can choose to provide a projection property (e.g., $foo) to expose more API for each wrapped property by defining a projectedValue property.
As with the wrappedValue property and init(wrappedValue:), the projectedValue property must have the same access level as its property wrapper type.
As property wrappers are new to Swift as a whole, documentation is still lacking on Apple's side in my opinion, especially when it comes to SwiftUI.
However, there are only a handful of property wrappers we really have to know about (#State, #Binding, #EnvironmentObject, #ObservedObject, #Published) to fully utilise SwiftUI; all other aspects of SwiftUI (such as View types) are pretty well documented in Apple's Developer docs.
Additionally, articles shared in the Swift community (example) can help you to get a grasp on where you might want to implement property wrappers yourself!
I know we can pass custom ObservableObjects types using the .environmentObject(_:) and then access it from subviews using #EnvironmentObject special properties.
But what it we want to pass non-custom, standard Int, String properties around views?
The only candidate I can see is:
func environment<V>(_ keyPath: WritableKeyPath<EnvironmentValues, V>, _ value: V) -> some View
But it seems that it only works for fixed, non-custom, KeyPaths such as \.colorScheme.
In other words I am looking to pass around a #State using the Environment.
TL;DR
#State is designed to be used in a single view (or passed via Binding to a descendant view). It is just a convenience to avoid making an ObservableObject class for every view. But if you are passing data to multiple views that are not tightly connected, you should wrap your data in an ObservableObject class and pass around with .environmentObject()
Explanation
To elaborate on krjw's comment, and why you can't pass around #State properties, let's talk about what the #State, #ObservedObject, #EnvironmentObject, #Published, and #Binding property wrappers are actually doing.
Because your view is a struct, it is a value type. When your app's state changes, and views must be shown differently as a result, your current view struct is thrown away, and a new one created in its place. But this means that data cannot be stored inside the struct, because it would get thrown away and replaced by an initial value every time the view changes.
Enter the ObservableObject protocol. When you create an object that conforms to this protocol, and mark one of its properties #Published, this is just nice syntactic sugar to say, "I've got this reference object, which I intend to represent state; please set up some Combine publishers that broadcast any time there's a change." Because it's a reference type, it can sit somewhere in memory, outside of our views, and as long as our views have a reference to it and subscribe to its publisher, they can know when it changes, and refresh accordingly.
But remember, we said that our views are structs. Even if you assigned one of their properties a reference to the ObservableObject and subscribed to its publishers, we would lose that information whenever the struct was recreated. This is where the #ObservedObject wrapper comes in. In effect, all that's doing is saying, "Store the reference and subscription to this object outside my struct, and when my view is recreated, make sure I know I'm supposed to reference and listen to this object."
The #EnvironmentObject does the same thing, it is just nice syntactic sugar to avoid having to pass in an ObservedObject via initializer. Instead, it says, "Trust me, one of your ancestors will have an object of this type. Just go ask them for it."
So really, that would be enough to do everything we need to do in SwiftUI. But what if I just need to store and watch a Bool for changes, and only need it in a single view? It's a bit heavy handed to make a whole class type just for that. That's where the #State property wrapper comes in. It is basically just combining the functions of the ObservedObject/#Published stuff with the #ObservedObject stuff. It says, "Keep this data outside my struct's memory (so it isn't thrown away on refresh), and notify me when anything changes." That's why you can modify your #State properties, even though modifying a struct's properties should recreate the struct; the data is not really inside your struct.
#State is just a condensed, convenient way to get the functionality of an ObservableObject class with a #ObservedObject wrapper, that will only be used in one struct.
For completeness, let's talk about #Binding. Sometimes, a direct descendent of our view may need to modify the #State variable of its ancestor (e.g., a TextField needs to modify the String value held by its parent view). The #Binding property wrapper just means, "You don't need to store this data (because its already stored by another view/object); you just need to look at it for changes and be able to write changes back."
NOTE: Yes, under the hood, #State is not literally setting up a class conforming to ObservableObject and another #ObservedObject wrapper; its much more efficient than that. But functionally, from a usage perspective, that's what's going on.
When it is needed, I pass #State via arguments or constructor as Binding. You can find wide usage of this approach in my code for post How do I create a multiline TextField in SwiftUI?