I have a view in SwiftUI, and I would like it to both redraw and to run a closure whenever a variable in my model changes. I am using this closure to update a state var I am storing in the view, which should be the previous value of the variable in my model before it changes
The following code simulates my situation:
let viewModel = ViewModel()
struct someView: View {
@observedObject var viewModel: ViewModel = viewModel
@State var previousSomeValue: CGFloat = 0
var body: some View {
Text("\(viewModel.model.someValue)")
}
}
class ViewModel: ObservableObject {
@Published var model = Model()
}
struct model {
var someValue: CGFloat = 0
}
With this setup, if someValue ever changes, someView redraws, however, I am unable to fire a closure.
//Solutions I have tried:
The main one was to attach onChangeOf(_ (T)->Void)
to my view. With .onChangeOf( viewModel.model.someValue ) { _ in //do something }
I was able to fire a closure whenever it changed however, by the time it ran, viewModel.model.someValue had already updated to the newValue, and I wasnt able to capture the old one. I read in the documentation that this is by design and that you must capture the thing you want to store the old value of, but I (to my knowledge) am only able to capture self, viewModel, but not viewModel.model.someValue.
.onChangeOf( viewModel.model.someValue ) { [self] newValue in //do something } //works but doesnt capture the var
.onChangeOf( viewModel.model.someValue ) { [viewModel] newValue in //do something } //works but doesnt capture the var
.onChangeOf( viewModel.model.someValue ) { [viewModel.model.someValue] newValue in //do something } //does not compile ( Expected 'weak', 'unowned', or no specifier in capture list )
I have also tried creating a binding in the view such as Binding { gameView.model.someValue } set: { _ in }
and having the onChange observer this instead, but even when I capture self, when the closure is called, the old and new values are identicial.
This seems like a common thing to do (detecting external changes and firing a closure), how should I go about it?
If I correctly understood your needs then you should do this not in view but in view model, like
class ViewModel: ObservableObject {
var onModelChanged: (_ old: Model, _ new: Model) -> Void
@Published var model = Model() {
didSet {
onModelChanged(oldValue, model)
}
}
init(onModelChanged: @escaping (_ old: Model, _ new: Model) -> Void = {_, _ in}) {
self.onModelChanged = onModelChanged
}
}
so instantiating ViewModel
you can provide a callback to observe values changed in model and have old and new values, like
@StateObject var viewModel = ViewModel() {
print("Old value: \($0.someValue)")
print("New value: \($1.someValue)")
}