svenyonson
svenyonson

Reputation: 1929

How to change state of SwiftUI Toggle externally

I am trying to change the state of a Toggle from outside the view. I've tried ObservableObject and EnvironmentObject, but the toggle is expecting a Binding (@State).

This code does let me execute the callback via the extension, but I cannot figure out how to change the State variable isOn externally, and if I was able to, I'm guessing my callback would be executed, which I don't want to happen.

import SwiftUI

struct ControlView: View {
    var title: String
    var panel: Int
    var callback: ()-> Void
    @State public var isOn = false // toggle state
    @EnvironmentObject var state: MainViewModel

    //@ViewBuilder
    var body: some View {
        VStack() {
             
            // -- Header
            HStack() {
                Text(" ")
                Image(self.state.panelIcon(panel: panel)).resizable().frame(width: 13.0, height: 13.0)
                Text(title)
                Spacer()
            }.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
            .background(Color(red: 0.9, green: 0.9, blue: 0.9))
            
            // -- Switch

            Toggle(isOn: $isOn.didSet { (state) in
                // Activate ARC
                callback()
            }) {
                Text("Enable ARC")
            }.padding(EdgeInsets(top: 0, leading: 12, bottom: 10, trailing: 12))

        }.overlay(
            RoundedRectangle(cornerRadius: 10)
                .stroke(Color(red: 0.8, green: 0.8, blue: 0.8), lineWidth: 1.25)
        ).background(Color.white)
    }
}

extension Binding {
    func didSet(execute: @escaping (Value) -> Void) -> Binding {
        return Binding(
            get: { self.wrappedValue },
            set: {
                self.wrappedValue = $0
                execute($0)
            }
        )
    }
}

Upvotes: 2

Views: 3035

Answers (3)

malhal
malhal

Reputation: 30746

You can use onChange to trigger a side effect as the result of a value changing, such as a Binding. e.g.

.onChange(of:isOn) {
     if isOn {
         model.sideEffectCount += 1
     }
     model.enabled = isOn
}

Upvotes: 1

user18176479
user18176479

Reputation:

This may not fit all needs, but if you want a dead simple way to externally change a toggle, i.e.:

Toggle("Gradient Button Control", isOn: $buttonDisabled)
            .toggleStyle(.switch)

you can just change the isOn var value:

//set toggle to on
self.buttonDisabled = true
//set toggle to off
self.buttonDisabled = false 

it's not perfect, there's no smooth animation, so it's a bit of a brute force method, but it is very simple.

Upvotes: 0

rob mayoff
rob mayoff

Reputation: 385970

You're on the right track by creating a custom Binding with a set function that performs your side effect. But instead of using a State, create a custom Binding that directly modifies the enabled property of your ObservableObject. Example:

import PlaygroundSupport
import SwiftUI

class MyModel: ObservableObject {
    @Published var enabled: Bool = false
    @Published var sideEffectCount: Int = 0
}

struct RootView: View {
    @EnvironmentObject var model: MyModel

    var body: some View {
        List {
            Text("Side effect count: \(model.sideEffectCount)")

            Button("Set to false programmatically") {
                model.enabled = false
            }

            Button("Set to true programmatically") {
                model.enabled = true
            }

            Toggle("Toggle without side effect", isOn: $model.enabled)

            Toggle("Toggle WITH side effect", isOn: Binding(
                get: { model.enabled },
                set: { newValue in
                    withAnimation {
                        if newValue {
                            model.sideEffectCount += 1
                        }
                        model.enabled = newValue
                    }
                }
            ))
        }
    }
}

PlaygroundPage.current.setLiveView(
    RootView()
        .environmentObject(MyModel())
)

Upvotes: 7

Related Questions