Home > Mobile >  SwiftUI: Action not triggering with toggle
SwiftUI: Action not triggering with toggle

Time:10-27

I'm trying to trigger an action with a toggle switch. In this case the print message "value did change" doesn't show up on console after clicking on the toggle.

This is targeted for a macOS 10.15 app, .onChange will not work.

struct ContentView: View {
    @State private var isToggle : Bool = false {
            didSet {
                print("value did change")
            }
    }

    var body: some View {
        Toggle(isOn: self.$isToggle){
                    Text("Toggle Label ")
         }
    }
}

CodePudding user response:

I am going to shamelessly steal the first part of @Asperi's answer, but the second part is mine...

@State private var isToggle : Bool = false

var body: some View {
    Toggle(isOn: self.$isToggle.onUpdate({
        print("value did change")   // << here !!
    })){
        Text("Toggle Label ")
    }
}

extension Binding {
    
    /// Adds a modifier for this Binding that fires an action when a specific
    /// value changes.
    ///
    /// You can use `onUpdate` to trigger a side effect as the result of a
    /// `Binding` value changing.
    ///
    /// `onUpdate` is called on the main thread. Avoid performing long-running
    /// tasks on the main thread. If you need to perform a long-running task in
    /// response to `value` changing, you should dispatch to a background queue.
    ///
    /// The new value is NOT passed into the closure.
    ///
    ///     struct PlayerView: View {
    ///         var episode: Episode
    ///         @State private var playState: PlayState = .paused
    ///
    ///         var body: some View {
    ///             VStack {
    ///                 Text(episode.title)
    ///                 Text(episode.showTitle)
    ///                 PlayButton(playState: $playState.updated {
    ///                     model.playStateDidChange.update()
    ///                 })
    ///             }
    ///         }
    ///     }
    ///
    /// - Parameters:
    ///   - action: A closure to run when the value changes.
    ///
    /// - Returns: A new binding value.

    func onUpdate(_ action: @escaping () -> Void) -> Binding<Value> {
        Binding(get: {
            wrappedValue
        }, set: { newValue in
            wrappedValue = newValue
            action()
        })
    }
}

extension Binding {

    /// Adds a modifier for this Binding that fires an action when a specific
    /// value changes.
    ///
    /// You can use `updated` to trigger a side effect as the result of a
    /// `Binding` value changing.
    ///
    /// `updated` is called on the main thread. Avoid performing long-running
    /// tasks on the main thread. If you need to perform a long-running task in
    /// response to `value` changing, you should dispatch to a background queue.
    ///
    /// The new value is passed into the closure.
    ///
    ///     struct PlayerView: View {
    ///         var episode: Episode
    ///         @State private var playState: PlayState = .paused
    ///
    ///         var body: some View {
    ///             VStack {
    ///                 Text(episode.title)
    ///                 Text(episode.showTitle)
    ///                 PlayButton(playState: $playState.updated { newState in
    ///                     model.playStateDidChange(newState)
    ///                 })
    ///             }
    ///         }
    ///     }
    ///
    /// - Parameters:
    ///   - action: A closure to run when the value changes.
    ///
    /// - Returns: A new binding value.
    func updated(_ action: @escaping (_ value: Value) -> Void) -> Binding<Value> {
        Binding(get: {
            wrappedValue
        }, set: { newValue in
            wrappedValue = newValue
            action(newValue)
        })
    }
}

There are two Binding extensions to use from iOS 13, watchOS 6 and macOS 10. The first .onUpdate() fires when the binding value changes, but does not give you access to the old or new values. It is JUST for side effects. I used this one above simply because the print() did not need any other value.

If you need to use the newValue in your closure, use .updated. It works very similarly to .onChange(of:) except it modifies the Binding and does not give you access to the old value.

CodePudding user response:

In should not (at least never worked in such way), because Toggle changes not state itself (by direct assignment to property) but wrapped value via binding... use instead

@State private var isToggle : Bool = false

var body: some View {
    Toggle(isOn: self.$isToggle){
         Text("Toggle Label ")
     }
     .onChange(of: isToggle) { _ in
         print("value did change")   // << here !!
     }
}
  • Related