Reputation: 1929
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
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
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
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