The SwiftUI tutorial uses the #State keyword to indicate mutable UI state:
#State var showFavoritesOnly = false
It offers this summary:
State is a value, or a set of values, that can change over time, and that affects a view’s behavior, content, or layout. You use a property with the #State attribute to add state to a view.
What does the keyword mean, exactly?
How does mutating a #State variable cause the view to be recomputed?
How are other variables immutable within the body getter?
The #State keyword is a #propertyWrapper, a feature just recently introduced in Swift 5.1. As explained in the corresponding proposal, it's sort of a value wrapper avoiding boilerplate code.
Sidenote: #propertyWrapper has previously been called #propertyDelegate, but that has changed since. See this post for more information.
The official #State documentation has the following to say:
SwiftUI manages the storage of any property you declare as a state.
When the state value changes, the view invalidates its appearance and
recomputes the body. Use the state as the single source of truth for a
given view.
A State instance isn’t the value itself; it’s a means of
reading and mutating the value. To access a state’s underlying value,
use its value property.
So when you initialize a property that's marked #State, you're not actually creating your own variable, but rather prompting SwiftUI to create "something" in the background that stores what you set and monitors it from now on! Your #State var just acts as a delegate to access this wrapper.
Every time your #State variable is written, SwiftUI will know as it is monitoring it. It will also know whether the #State variable was read from the View's body. Using this information, it will be able to recompute any View having referenced a #State variable in its body after a change to this variable.
Let me add something else if you know React Native.
The #State property is very like the this.state object in React Native.
For example:
struct Foobar: some View {
#State var username = ""
}
class Foobar extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
};
}
}
When you modify the username variable, they will have the same effect, that re-render the current page.
Its explained nicely with an example in the WWDC video - Session 204 (starts at 16:00, quotation starts at 20:15)
One of the special properties of #State variables is that SwiftUI can observe when they're read and written. Because SwiftUI knows that zoomed was read in body, it knows that the view's rendering depends on it. Which means - when a variable changes the framework is going to ask for body again using the new #State value.
The #State as a Property Wrapper is also elaborated and justified in Data Flow Through Swift UI (5:38) WWDC vid as well. It's shown how it solves the problem when we need a mutable value in an immutable (struct) View.
If you click into #State you can see that it has several getters. One with Value another with Binding<Value>.
SwiftUI seems to rely heavily on reactive programming (and their new Combine framework, and since we cannot see the full implementation of these wrappers, I would expect that the values that are stored through #State property wrappers are being managed by a CurrentValueSubject from Combine. Like the name implies, this essentially stores the current value, which can then be used as a bindable property by using the $ syntax.
I like how profesor Paul Hegarty from Stanford explain it. He said that #State basically convert that variable into a pointer to some boolean somewhere else in memory and that's where the value change. But your variable - now a pointer with #State- does not change, is always pointing to that place in memory.
Note:- The accepted answer is correct but incase you are trying to look for a easier explanation I tried below
#State property wrapper allow us to change values inside a struct.
Note:- You cannot change a property within a struct as its a values types, so its not allowed.
struct ExampleTextView: View {
// Declare a variable with state property wrapper
#State private var shouldChangeText: Bool = false
var body: some View {
Text("Just an example")
.foregroundColor(Color.white)
}
}
Incase you are wondering should we add a private access specifier?
Yes, as a rule of thumb apple recommends #State propertise shouldn't be
shared with other views as there are more specific property wrappers for
that #ObservedObject and #EnvironmentObject.
But if it's value type how it can be mutated where is it stored then?
So whenever we are marking a property with #State we move its storage out
from the struct into the shared storage managed by SwiftUI.
In case in your mind if you are comparing with Swift, this is how its done there,
struct Example {
var exampleType: String
mutating func changeType() {
exampleType = "Are we allowed to do this?"
}
}
Credit: Please note this answer is inspired by this post https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-state-property-wrapper
If you know about C# and windows development. #State is similar if not the same as x:Bind or Binding.. On a collection it is similar if not the same as ObservableCollection.
As fredpi said, SwiftUI is listing for updates on vars with the #State property delegate.
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'm working on a SwiftUI app using the MVVM architecture. I have the problem that I need to pass data from a parent View into its child viewModel, but I'm not sure how to pass a parameter from the view into the viewModel.
The child View which should own the viewModel is here:
struct InstanceView: View {
#ObservedObject var instance: Instance
#StateObject var viewModel = InstanceViewViewModel(instance: instance)
var body: some View {
...
}
}
And this is the viewModel:
class InstanceViewViewModel: ObservableObject {
#ObservedObject var instance: Instance
...
}
Obviously the View doesn't work, I get the error Cannot use instance member 'instance' within property initializer; property initializers run before 'self' is available
If I try using init() in the View to assign a value to viewModel:
#ObservedObject var instance: Instance
#StateObject var viewModel: InstanceViewViewModel
init(instance: Instance) {
self.instance = instance
self.viewModel = InstanceViewViewModel(instance: instance)
}
But I get the error Cannot assign to property: 'viewModel' is a get-only property.
I have read that you should give the viewModel property (instance) a default value, and then set it in the View with .onAppear(). However, in my case, Instance is a Core Data object so I can't really create one to use as a default value.
I also read that I could maybe use _instance = StateObject(wrappedValue: InstanceViewViewModel(instance: instance)) in the View's init, and swiftUI would be smart enough to only initialize the StateObject once. However I'm pretty sure this is a bad practice.
I also can't use lazy on a StateObject
So is there any other way to achieve this? Could a solution be somehow getting the value InstanceView is being initialized with on this line:
#StateObject var viewModel = InstanceViewViewModel(instance: instance)
, outside of an init, so I don't need to reference self, like you would do inside of an init? (this is probably a stupid idea)
Or should I be implementing MVVM in a different way?
Thanks!
SwiftUI property wrappers are often automagic. #StateObject modifies the property in a way that the initialization has to be at the point of declaration. The pattern suggested by Apple is to create the #StateObject in the parent of the view and pass it down either as an #ObservedObject or #EnvironmentObject.
It's not good practice to use MVVM view model objects with SwiftUI because in SwiftUI, the View struct value type is designed to hold the view data which is regarded as less buggy than using objects. The use of property wrappers, e.g. #State give these value types similar semantics to reference types, i.e. objects, so we get the best of both worlds. If you move your view data into view model objects instead of using the View struct correctly, then you lose these features and re-introduce the consistency bugs that SwiftUI was designed to eliminate. These structs form an efficiently updated (via dependency tracking) and diffed hierarchy and SwiftUI internally takes the result and they update UIKit (or AppKit) controls for us in a platform specific way, we don’t interact with the actual UIViews, NSView i.e. the V in the traditional MVC/MVVM sense at all.
The ‘ObservableObject’ protocol for reference types is part of the Combine framework so we only use that when we require a Combine pipeline which doesn’t appear to be required in your case. ‘#StateObject’ defines a source of truth, thus it cannot possibly take any parameters because then it wouldn’t be a source of truth.
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?
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".