Having a SwiftUI project generated by Xcode, and adding a custom MyView
with a MyViewModel
.
The ContentView
just renders MyView
.
The problem:
ContentView
gets reloaded (the reload button changes its state), MyViewModel
gets somehow disconnected from MyView
(the MyView
counter stops incrementing in the UI when the button is clicked), but the console logs show the incrementation works.Looking at the instance counters and memory addresses in the console:
ContentView
gets refreshed, a new MyView
and MyViewModel
instances get created. However, the counter incrementation uses the original first-created model instance.EDIT: The model needs to be recreated every time MyView
is recreated.
import SwiftUI
struct ContentView: View {
@State
private var reloadCounter = 0
var body: some View {
VStack {
Button(action: { self.reloadCounter += 1 },
label: { Text("Reload view") })
Text("Reload counter: \(reloadCounter)")
MyView().environmentObject(MyViewModel())
}
}
}
import SwiftUI
struct MyView: View {
@EnvironmentObject
private var model: MyViewModel
var body: some View {
VStack {
Button(action: { self.model.counter += 1 },
label: { Text("Increment counter") })
Text("Counter value: \(model.counter)")
}
.frame(width: 480, height: 300)
}
init() { withUnsafePointer(to: self) { print("Initialising MyView struct instance \(String(format: "%p", $0))") }}
}
import Combine
class MyViewModel: ObservableObject {
private static var instanceCount: Int = 0 { didSet {
print("SettingsViewModel: \(instanceCount) instances")
}}
@Published
var counter: Int = 0 { didSet {
print("Model counter: \(counter), self: \(Unmanaged.passUnretained(self).toOpaque())")
}}
init() { print("Initialising MyViewModel class instance \(Unmanaged.passUnretained(self).toOpaque())"); Self.instanceCount += 1 }
deinit { print("Deinitialising MyViewModel class instance \(Unmanaged.passUnretained(self).toOpaque())"); Self.instanceCount -= 1 }
}
Any clue what did I do wrong?
The image below depicts the app's UI after all the actions in the logs were performed.
In the latest Xcode (tested with Xcode 14.3.1) the model leaking does not happen anymore.
Also, there is the @StateObject property wrapper available that allows views to instantiate the models.