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.
Related
I am using CoreData in a SwiftUI project. I want to create an optional var foo that holds an Entity that will be passed to all the views through .environmentObject(). If foo has a value I would like to display a view that will exist across the entire application otherwise the view is not visible.
Problems I have run into. I attempted to make WorkoutTemplate optional
#StateObject private var activeWorkout: WorkoutTemplate?
Generic struct 'StateObject' requires that 'WorkoutTemplate?' conform to 'ObservableObject'
WorkoutTemplate conforms to ObservableObject since it is (NSManagedObject)
Attempting to use an initializer on activateWorkout does not work as seen in the attached image:
Variable 'self.activeWorkout' used before being initialized
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?
I am currently learning about how delegates work, however I cannot understand why a variable is initialized as a type of the class, and not instantiated. How can a variable be a type of a class and why is that necessary when you could just instantiate the class?
var person: person!
How can a variable be a type of a class and why is that necessary when you could just instantiate the class?
You obviously can just create an instance of the class:
var person = Person(...) // Note, class names generally begin with uppercase letter
But consider the following:
var person: Person!
This means that person is now an Optional, one that can refer to a Person instance. And the ! indicates that this optional will be implicitly unwrapped when you reference it in your code.
So the question is why would you use the latter (the implicitly unwrapped reference which is not yet set) rather than the former (just instantiating the Person immediately). The answer is that you generally do this when the person variable simply cannot be set initially, but will be set later.
A common example would be a “details” view controller which will show us details about a Person object selected from a prior view controller. If this details view controller is instantiated from, say, a storyboard scene, clearly the person variable isn’t set yet, so it has to be an optional. But the presenting view controller (or the coordinator or whatever) will set the person reference after the scene’s view controller is first instantiated, but before the view controller appears on the screen. E.g., we might set the destination’s person inside the presenting view controller’s prepare(for:sender:) method.
So, in this case, we would declare person to be an Optional (so the view controller can be instantiated, even though the person hasn’t been set yet), but declare it to be an implicitly unwrapped one (Person!) because we know the presenter will make sure to set person before the details view controller appears on screen.
The statement referenced is a variable declaration without an initializer expression. The normal format is:
var variable name: type = expression
The colon followed by type is a type annotation, while the expression that follows the equal sign is the actual initialization expression.
var person: Person!
So here we are declaring a variable named person, annotated with a type of Person, and the exclamation point marks it as an implicitly unwrapped optional.
There are probably several reasons why a variable (whether class or struct or some other type) might be declared without also being initialized:
An example is when working with IBOutlet variables that are declared inside a view controller class. These variables are usually declared as implicitly unwrapped optionals. When the view controller object is first created, these variables are not yet initialized. However, they are always set by the time viewDidLoad is invoked.
Another reason might be that it just wouldn't make sense in terms of the application logic to instantiate the variable immediately. Lazy initialization is a special case of this where the variable in question is automatically initialized at the first point it is needed.
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.