In my code, the "final" toggle's value is based on the complex logic inside the view model. For example, user turns the toggle on but the logic can turn it off if certain conditions are not satisfied (here below simplified to number comparison).
Problem: after user turns the toggle on, onChange
is fired and if the logic finds out it should be turned off, the onChange
will be fired once again because there is a change (this time not made by the user).
For example, if the generated random number is 5, then the console prints the following statements (8 comes from the second call):
on change called
shouldUseToggle called
5
on change called
shouldUseToggle called
8
onChange(of: Bool) action tried to update multiple times per frame.
I would like to avoid it and make onChange
react only to the user's change, not the change that comes from the view model. Is there a way to do this ? Or maybe other way to solve it ?
import SwiftUI
struct ContentView: View {
@StateObject var myViewModel = MyViewModel()
var body: some View {
VStack(spacing: 0) {
Toggle("Use xyz ?", isOn: $myViewModel.turnToggleOn).onChange(of: myViewModel.turnToggleOn, perform: { userTurnedOn in
print("on change called")
myViewModel.shouldUseToggle(userTurnedOn: userTurnedOn)
})
}
}
}
class MyViewModel: ObservableObject {
@Published var turnToggleOn = false
func shouldUseToggle(userTurnedOn: Bool) {
///some complex logic comes here, for simplicity I use random numbers
print("shouldUseToggle called")
let x = Int.random(in: 0..<10)
print(x)
if userTurnedOn {
turnToggleOn = x > 5
} else {
turnToggleOn = x > 3
}
}
}
CodePudding user response:
Use custom binding and call your logic function direct from binding.
struct ContentView: View {
@StateObject var myViewModel = MyViewModel()
var body: some View {
VStack(spacing: 0) {
Toggle("Use xyz ?", isOn: Binding(get: {myViewModel.turnToggleOn}, set: {
myViewModel.turnToggleOn = $0;
myViewModel.shouldUseToggle(userTurnedOn: $0)
}))
}
}
}
class MyViewModel: ObservableObject {
var turnToggleOn = false
func shouldUseToggle(userTurnedOn: Bool) {
///some complex logic comes here, for simplicity I use random numbers
print("shouldUseToggle called")
let x = Int.random(in: 0..<10)
print(x)
if userTurnedOn {
turnToggleOn = x > 5
} else {
turnToggleOn = x > 3
}
}
}