hbr4u7vb2sljksdf2
hbr4u7vb2sljksdf2

Reputation: 318

SwiftUI avoid repeated onChange call

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
        }
    }
}

Upvotes: 1

Views: 3654

Answers (1)

Raja Kishan
Raja Kishan

Reputation: 18924

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
        }
    }
}

Upvotes: 2

Related Questions