Obervableobject Being Init Multiple Time, and Not Refreshing My View

ObervableObject being init multiple time, and not refreshing my view

If your @ObservedObject is being initialized multiple times, it is because the owner of the object is refreshed and recreated every time it has state changes. Try to use @StateObject if your app is iOS 14 and above. It prevents the object from being recreated when the view refreshes.
https://developer.apple.com/documentation/swiftui/stateobject

When a view creates its own @ObservedObject instance it is recreated
every time a view is discarded and redrawn. On the contrary a @State
variable will keep its value when a view is redrawn. 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

What is the difference between ObservedObject and StateObject in SwiftUI

SwiftUI - ObservableObject created multiple times

Latest SwiftUI updates have brought solution to this problem. (iOS 14 onwards)

@StateObject is what we should use instead of @ObservedObject, but only where that object is created and not everywhere in the sub-views where we are passing the same object.

For eg-

class User: ObservableObject {
var name = "mohit"
}


struct ContentView: View {
@StateObject var user = User()

var body: some View {
VStack {
Text("name: \(user.name)")
NameCount(user: self.user)
}
}
}


struct NameCount: View {
@ObservedObject var user

var body: some View {
Text("count: \(user.name.count)")
}
}

In the above example, only the view responsible (ContentView) for creating that object is annotating the User object with @StateObject and all other views (NameCount) that share the object is using @ObservedObject.

By this approach whenever your parent view(ContentView) is re-created, the User object will not be re-created and it will persist its @State, while your child views just observing to the same User object doesn't have to care about its re-creation.

SwiftUI how to get rid of constant initialization of views linked through NavigationLink in NavigationView?

As lorem ipsum said, the initialization and updates of the content is part of how SwiftUI works and is to be expected. To overcome that, just create a new view that handle the timer, so that only your this last will be updated. Your ContentView should look more like :

struct ContentView: View {
var body: some View {
NavigationView{
VStack{
CounterView()
NavigationLink(destination: ListView()) {
Text("List").padding()
}
}
}
}
}

And your CounterView :

struct CounterView: View {
@State private var counter = 0
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

var body: some View {
Text("counter: \(counter)")
.onReceive(timer) { _ in
counter += 1
}
}
}

However, if you need to access your Timer variable through out your without necessarily update each single view, you might consider making a singletone accessible by calling simply "Timer.secondsUpdater".

extension Timer {
static let secondsUpdater = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
}

Your CounterView would endup calling timer like this :

struct CounterView: View {
@State private var counter = 0

var body: some View {
Text("counter: \(counter)")
.onReceive(Timer.secondsUpdater) { _ in
counter += 1
}
}
}

Hope it was helpful /p>

What is the difference between ObservedObject and StateObject in SwiftUI

@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?

SwiftUI app goes back to first screen when app is put into the background

There are two solutions (that came to my mind, and do not cause a lot of changes) that you can apply:

1. Injection

When you are initializing the 'LaunchViewControl' class here in the view:

@ObservedObject var control: LaunchViewControl = LaunchViewControl()

SwiftUI initializes this control variable again and again when its view discarded and redrawn, because ObservedObjects are not as same as State variables, and SwiftUI won't keep track of their states.

To try this out, uncomment the line for 'scenePhase' and add an 'init' method to 'LaunchViewControl', then add some print statements to understand what's going on, or add a breakpoint.

For example:

final class LaunchViewControl: ObservableObject {
// ....
init() {
print("initialized")
}
// ....
}

You will see multiple "initialized" logs on the console. Which means that, your 'isReady' variable will be overwritten to 'false', again and again.

To prevent this and keep lifecyle events, you can use a basic injection.

For example, change your 'LaunchView' to:

struct LaunchView: View {

// Change here
@ObservedObject var control: LaunchViewControl

var body: some View {
NavigationView(content: {
ZStack {
//some view hierarchy
Text("Launch")
NavigationLink(destination: RootView(),
isActive: $control.isReady,
label: {EmptyView()})

}
.onAppear(perform: {
control.loadData()
})
.navigationBarTitle("")
.navigationBarHidden(true)
.statusBar(hidden: true)
}).navigationViewStyle(StackNavigationViewStyle())
}
}

and your 'Jam500App' to:

@main
struct Jam500App: App {

@Environment(\.scenePhase) var scenePhase

let launchViewControl = LaunchViewControl()

var body: some Scene {
WindowGroup {
// Inject launchViewControl
LaunchView(control: launchViewControl)
}
}
}

You should now see your second view is not popped and you have also kept the lifecycle events.

2. StateObject

Instead of doing these all, you can change your ObservedObject to a 'StateObject', which will keep its state when view discarded and redrawn.

More information

Also, there are some great answers already in SO that you can check about ObservedObject's and how they works. One of them is this. And another one about the differences between StateObject and ObservedObject is this.

To prevent this in further steps of the project, I would give MVVM approach a try, and if you want to check more about it, there are great tutorials out there, for example this.



Related Topics



Leave a reply



Submit