swifttoggleswiftui

How can I trigger an action when a toggle() is toggled?


In my SwiftUI view I have to trigger an action when a Toggle() changes its state. The toggle itself only takes a Binding. I therefore tried to trigger the action in the didSet of the @State variable. But the didSet never gets called.

Is there any (other) way to trigger an action? Or any way to observe the value change of a @State variable?

My code looks like this:

struct PWSDetailView : View {

    @ObjectBinding var station: PWS
    @State var isDisplayed: Bool = false {
        didSet {
            if isDisplayed != station.isDisplayed {
                PWSStore.shared.toggleIsDisplayed(station)
            }
        }
    }

    var body: some View {
            VStack {
                ZStack(alignment: .leading) {
                    Rectangle()
                        .frame(width: UIScreen.main.bounds.width, height: 50)
                        .foregroundColor(Color.lokalZeroBlue)
                    Text(station.displayName)
                        .font(.title)
                        .foregroundColor(Color.white)
                        .padding(.leading)
                }

                MapView(latitude: station.latitude, longitude: station.longitude, span: 0.05)
                    .frame(height: UIScreen.main.bounds.height / 3)
                    .padding(.top, -8)

                Form {
                    Toggle(isOn: $isDisplayed)
                    { Text("Wetterstation anzeigen") }
                }

                Spacer()
            }.colorScheme(.dark)
    }
}

The desired behaviour would be that the action "PWSStore.shared.toggleIsDisplayed(station)" is triggered when the Toggle() changes its state.


Solution

  • iOS 17+

    In iOS 17 onChange with a single parameter is deprecated - instead we should:

    Use onChange with a two or zero parameter action closure instead.

    struct ContentView: View {
        @State private var isDisplayed = false
        
        var body: some View {
            Toggle("", isOn: $isDisplayed)
                .onChange(of: isDisplayed) {
                    print("Action")
                }
                .onChange(of: isDisplayed) { oldValue, newValue in
                    // action...
                    print(oldValue, newValue)
                }
        }
    }
    

    We can also set the initial parameter to specify whether the action should be run when the view initially appears.

    struct ContentView: View {
        @State private var isDisplayed = false
        
        var body: some View {
            Toggle("", isOn: $isDisplayed)
                .onChange(of: isDisplayed, initial: true) {
                    print("Action")
                }
        }
    }
    

    iOS 14+

    If you're using iOS 14 and higher you can use onChange:

    struct ContentView: View {
        @State private var isDisplayed = false
        
        var body: some View {
            Toggle("", isOn: $isDisplayed)
                .onChange(of: isDisplayed) { value in
                    // action...
                    print(value)
                }
        }
    }