1

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
        }
    }
}
2
  • 1
    It might be a better experience for the user if an option is not available to be turned on, that control should be disabled. In other words, use to complex logic to determine if the toggle should be enabled rather than run after the user attempts to change it. Commented Oct 13, 2021 at 22:21
  • good point, but for a typical case - in my case I additionally show an alert with the reason why it is not available which would not be possible if the toggle is disabled Commented Oct 14, 2021 at 11:35

1 Answer 1

2

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
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.