SwiftUI observed object do action when its property change - ios

I have observed object which is created in view and I want to have function that will occur when the object bool property is changed. I want something similar to .onTapGesture. Is there a way to do it? have function where the body of the function is created outside of that object?

Let's replicate...
class Demo: ObservableObject {
#Published var value = false
}
struct DemoView: View {
#ObservedObject var demo = Demo()
var body: some View {
Text("Demo")
.onReceive(demo.$value) { flag in
// call here your function
}
}
}

use #ObservedObject to make change in value
an use #StateObject to read the published value

Related

How to pass data from a parent model class to a subview in SwiftUI

I am having my main view in ContentView class and I do the network call in ContentViewModel class and update a class call User with the response.
My ContentView has several subviews such as HeaderView. When I update the User class properties from the API response, I want to update UI elements defined in HeaderView as well. How can I pass data to my subview (HeaderView) from the ContentViewModel class?
I can update the ContentView elements which is pretty simple by setting the ContentViewModel class ObservableObject
class ContentViewModel: ObservableObject {
and Published the User object
#Published var user = User()
and update the attribute of user once I received the API response in the same ContentViewModel
self.user.name = name
where my ContentView has the relationship with it's model class like below
#StateObject private var contentViewModel = ContentViewModel()
and the UI updates as follow
Text("My name \(contentViewModel.user.name)")
That is the working scenario for ContentView. But I need to pass the value to HeaderView().
My user class is like below
import Foundation
class User {
var name = ""
// more attributes
}
this is the pattern I often use: (note no private var)
struct ContentView: View {
#StateObject var contentViewModel = ContentViewModel()
...
var body: some View {
...
HeaderView(contentViewModel: contentViewModel)
}
}
struct HeaderView: View {
#ObservedObject var contentViewModel: ContentViewModel
var body: some View {
...
Text("My name \(contentViewModel.user.name)")
}
}
struct User {
var name = ""
// more attributes
}
You can use the same idea with the ".environment(...)" pattern to pass ContentViewModel to all subviews.
Edit: as suggested by #Asperi User should be made a struct.
When I update the User class properties from the API response, I want to update UI
Independently of how do you pass your model to sub-view it will not update because your User is-a class, so changing its properties will not activate publishing (because published wrapper holds a reference to User which is not changed) and so view not updating.
Instead you should make User a struct
struct User {
var name = ""
// more attributes
}
or make it ObservableObject as well with all needed properties published and pass by reference to subview observing it in regular way.

ObservableObject is missing in Motherview

I have the following viewfile:
import SwiftUI
class Progress: ObservableObject {
#Published var index = 0
}
struct RegistrationView: View {
#ObservedObject var progress: Progress
var body: some View {
TabView(selection: $progress.index) {
setUsernameView(progress: self.progress).tabItem{}.tag(0)
setMasterPasswordView().tabItem{}.tag(1)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
}
}
when I am finished with my setUserNameView() I want to update index to 1 so that it switches to setMasterPasswordView. Im passing progress to the view and then inside to another view which updates the value.
Now xcode complains: Missing argument for parameter 'progress' in call the call is in the contentView().
import SwiftUI
struct ContentView: View {
var body: some View {
RegistrationView()
}
}
My question is, why does the view or any other view above RegistrationView need this object too and how do I save me from that work. I tried to make progress private but that doesn't work. I am defining the class and all other things in this view and I only need it for itself and the child of a child.
P.S. is there a better methode for observable objects? For example when I need the value in a main view and can change it in a child.child.child.view I have to pass it all the way down. And that seems kind of unnecessary work.
If you're not going to use it in parent view then just use locally as StateObject
struct RegistrationView: View {
#StateObject var progress = Progress() // << here !!
// ... other code

What is the difference between ObservedObject and StateObject in SwiftUI

If I have an ObservableObject in SwiftUI I can refer to it as an #ObservedObject:
class ViewModel: ObservableObject {
#Published var someText = "Hello World!"
}
struct ContentView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
Text(viewModel.someText)
}
}
Or as a #StateObject:
class ViewModel: ObservableObject {
#Published var someText = "Hello World!"
}
struct ContentView: View {
#StateObject var viewModel = ViewModel()
var body: some View {
Text(viewModel.someText)
}
}
But what's the actual difference between the two? Are there any situations where one is better than the other, or they are two completely different things?
#ObservedObject
When a view creates its own #ObservedObject instance it is recreated every time a view is discarded and redrawn:
struct ContentView: View {
#ObservedObject var viewModel = ViewModel()
}
On the contrary a #State variable will keep its value when a view is redrawn.
#StateObject
A #StateObject is a combination of #ObservedObject and #State - the instance of the ViewModel will be kept and reused even after a view is discarded and redrawn:
struct ContentView: View {
#StateObject var viewModel = ViewModel()
}
Performance
Although an #ObservedObject can impact the performance if the View is forced to recreate a heavy-weight object often, it should not matter much when the #ObservedObject is not complex.
When to use #ObservedObject
It might appear there is no reason now to use an #ObservedObject, so when should it be used?
You should use #StateObject for any observable properties that you
initialize in the view that uses it. If the ObservableObject instance
is created externally and passed to the view that uses it mark your
property with #ObservedObject.
Note there are too many use-cases possible and sometimes it may be desired to recreate an observable property in your View. In that case it's better to use an #ObservedObject.
Useful links:
What’s the difference between #StateObject and #ObservedObject?
What’s the difference between #ObservedObject, #State, and #EnvironmentObject?
What is the #StateObject property wrapper?
Apple documentation did explain why initializing with ObservedObject is unsafe.
SwiftUI might create or recreate a view at any time, so it’s important that initializing a view with a given set of inputs always results in the same view. As a result, it’s unsafe to create an observed object inside a view.
The solution is StateObject.
At the same time, the documentation showed us how we should create data models in a view (or app/scene) when it can hold on to the truth, and pass it to another view.
struct LibraryView: View {
#StateObject var book = Book() // Hold on to the 1 truth
var body: some View {
BookView(book: book) // Pass it to another view
}
}
struct BookView: View {
#ObservedObject var book: Book // From external source
}
Even though pawello2222's answer have nicely explained the differences when the view itself creates its view model, it's important to note the differences when the view model is injected into the view.
When you inject the view model into the view, as long as the view model is a reference type, there are no differences between #ObservedObject and #StateObject, since the object that injected the view model into your view should hold a reference to view model as well, hence the view model isn't destroyed when the child view is redrawn.
class ViewModel: ObservableObject {}
struct ParentView: View {
#StateObject var viewModel = ViewModel()
var body: some View {
ChildView(viewModel: viewModel) // You inject the view model into the child view
}
}
// Even if `ChildView` is discarded/redrawn, `ViewModel` is kept in memory, since `ParentView` still holds a reference to it - `ViewModel` is only released and hence destroyed when `ParentView` is destroyed/redrawn.
struct ChildView: View {
#ObservedObject var viewModel: ViewModel
}
Here is an example to illustrate the difference.
Every time you click the Refresh button the StateObjectClass is recreated from scratch only for CountViewObserved. This means it's #Published count property gets the default value of 0 when this happens.
The difference between #StateObject and #ObservedObject is clear. The #StateObject version of the observed StateObjectClass preserves its state since it is never deinitted. The #ObservedObject version does not as it is recreated. So you should use #StateObject for the owner of an ObservableObject.
import SwiftUI
class StateObjectClass: ObservableObject {
enum ObserverType: String {
case stateObject
case observedObject
}
#Published var count = 0
let type: ObserverType
let id = UUID()
init(type: ObserverType) {
self.type = type
}
deinit {
print(#function, "type: \(type.rawValue) id: \(id)")
}
}
struct CountViewState: View {
#StateObject var state = StateObjectClass(type: .stateObject)
var body: some View {
VStack {
Text("#StateObject's count: \(state.count)")
Button("ADD 1"){
state.count += 1
}
}
}
}
struct CountViewObserved: View {
#ObservedObject var state = StateObjectClass(type: .observedObject)
var body: some View {
VStack {
Text("#ObservedObject's count: \(state.count)")
Button("Add 1") {
state.count += 1
}
}
}
}
struct ContentView: View {
#State private var count = 0
var body: some View {
VStack {
Text("Refresh CounterView's count: \(count)")
Button("Refresh") {
count += 1
}
CountViewState()
.padding()
CountViewObserved()
.padding()
}
}
}
#StateObject is a state of a given view, thus the instance of it is retained by SwiftUI across body updates. It is not retained though when running in Preview.
#ObservedObject on the other hand is just an object being observed by given View, thus is not retained by SwiftUI (it has to be retained outside of the View).
In other words - it looks like SwiftUI keeps a strong reference of #StateObject and unowned reference of #ObservedObject.
Retained vs non-retained source, Previews behavior source, around ~8:30.
The difference between let's say :
#ObservedObject var book: BookModel
And
#StateObject var book: BookModel
#ObservedObject does NOT own the instance book, its your responsibility to manage the life cycle of the instance..
But when you want to tie the life cycle of your observable object book to your view like in #State you can use #StateObject.
In this case SwiftUI will OWN the observable object and the creation and destruction will be tied to the view's life cycle
SwiftUI will keep the object alive for the whole life cycle of the view
This is great for expensive resources, you do not need to fiddle with onDisappear anymore to release resources.
This clarification is taken from WWDC2020 Data essentials in SwiftUI

Observing Binding or State variables

I'm looking for a way of observing #State or #Binding value changes in onReceive. I can't make it work, and I suspect it's not possible, but maybe there's a way of transforming them to Publisher or something while at the same time keeping the source updating value as it's doing right now?
Below you can find some context why I need this:
I have a parent view which is supposed to display half modal based on this library: https://github.com/AndreaMiotto/PartialSheet
For this purpose, I've created a #State private var modalPresented: Bool = false and I'm using it to show and hide this modal view. This works fine, but my parent initializes this modal immediately after initializing self, so I completely loose the onAppear and onDisappear modifiers. The problem is that I need onAppear to perform some data fetching every time this modal is being presented (ideally I'd also cancel network task when modal is being dismissed).
use ObservableObject / ObservedObject instead.
an example
import SwiftUI
class Model: ObservableObject {
#Published var txt = ""
#Published var editing = false
}
struct ContentView: View {
#ObservedObject var model = Model()
var body: some View {
TextField("Email", text: self.$model.txt, onEditingChanged: { edit in
self.model.editing = edit
}).onReceive(model.$txt) { (output) in
print("txt:", output)
}.onReceive(model.$editing) { (output) in
print("editing:", output)
}.padding().border(Color.red)
}
}

What does Apple mean by #ObjectBinding should be passed over through view?

I've been newly studying SwiftUI.
And since I've seen Data flow over SwiftUI video from Apple explaining difference between #ObjectBinding and #EnvironmentObject, a question has come to my mind.
What does apple mean by :
You have to pass around the model from hop to hop in #ObjectBinding ? (29':00")
Do we have to pass the object using #binding in another views for using them ?
What if we don't use #binding and reference to it using another #ObjectBinding ?
Does that make an inconvenience or make SwiftUI not to work correctly or views not being sync with each other ?
[Edit: note that #ObjectBinding is no longer around; instead you can use #State to mark an instance variable as requiring a view refresh when it changes.]
When a view declares an #State or #Binding variable, its value must be explicitly passed from the view's parent. So if you have a long hierarchy of views with some piece of data from the top being used in the bottom, you must code every level of that hierarchy to know about and pass down the data.
In his comment at 29:00, he is contrasting this to using #EnvironmentVariable in a child view, which searches the whole hierarchy for that piece of data. This means any views that do not explicitly need the data can effectively ignore it. Registering a variable needs only be done once (via .environmentObject(_) on a view).
Here is a contrived example. Given some data type conforming to ObservableObject,
class SampleData: ObservableObject {
#Published var contents: String
init(_ contents: String) {
self.contents = contents
}
}
Consider this view hierarchy:
struct ContentView: View {
#State private var data = SampleData("sample content")
var body: some View {
VStack {
StateViewA(data: self.data)
EnvironmentViewA()
.environmentObject(self.data)
}
}
}
struct StateViewA: View {
#State var data: SampleData
var body: some View {
StateViewB(data: self.data)
}
}
struct StateViewB: View {
#State var data: SampleData
var body: some View {
Text(self.data.contents)
}
}
struct EnvironmentViewA: View {
var body: some View {
EnvironmentViewB()
}
}
struct EnvironmentViewB: View {
#EnvironmentObject var data: SampleData
var body: some View {
Text(self.data.contents)
}
}
The result in ContentView will be two views that display the same piece of text. In the first, StateViewA must pass the data on to its child (i.e. the model is passed from "hop to hop"), whereas in the second, EnvironmentViewA does not have to be aware of the data at all.

Resources