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
Wkwebview Does Not Load Links to Pdfs
Swift Sphere Combine Star Data
Alamofire Asynchronous Completionhandler For Json Request
Include Swiftui Views in Existing Uikit Application
Generating Random Numbers With Swift
Swiftui - Is There a Popviewcontroller Equivalent in Swiftui
What's the Difference Between Using Aranchor to Insert a Node and Directly Insert a Node
Generic Swift 4 Enum With Void Associated Type
Ios13 Navigation Bar Large Titles Not Covering Status Bar
How to Configure Contextmenu Buttons For Delete and Disabled in Swiftui
Extension May Not Contain Stored Property But Why Is Static Allowed
Can You Override Between Extensions in Swift or Not? (Compiler Seems Confused!)
Swift Spritekit Adding Button Programmatically
Spritekit Physics in Swift - Ball Slides Against Wall Instead of Reflecting