SwiftUI: Call function when toggle is tapped and the State of the variable changes
SwiftUI 2.0
Xcode 12 / iOS 14
As simple as
Toggle(isOn: $isOn) {
Text(isOn ? "On" : "Off")
}.onChange(of: isOn) {
self.sendState(state: $0)
}
SwiftUI 1.0+ (still valid)
A bit more complicated
Toggle(isOn: $isOn) {
Text(isOn ? "On" : "Off")
}.onReceive([isOn].publisher.first()) {
self.sendState(state: $0)
}
How can I run an action when a state changes?
You can't use didSet
observer on @State
but you can on an ObservableObject
property.
import SwiftUI
import Combine
final class SelectionStore: ObservableObject {
var selection: SectionType = .top {
didSet {
print("Selection changed to \(selection)")
}
}
// @Published var items = ["Jane Doe", "John Doe", "Bob"]
}
Then use it like this:
import SwiftUI
enum SectionType: String, CaseIterable {
case top = "Top"
case best = "Best"
}
struct ContentView : View {
@ObservedObject var store = SelectionStore()
var body: some View {
List {
Picker("Selection", selection: $store.selection) {
ForEach(FeedType.allCases, id: \.self) { type in
Text(type.rawValue).tag(type)
}
}.pickerStyle(SegmentedPickerStyle())
// ForEach(store.items) { item in
// Text(item)
// }
}
}
}
How to trigger a function via variable change (binding)
You can do the following, create this extension
extension Binding {
func didSet(execute: @escaping (Value) ->Void) -> Binding {
return Binding(
get: {
return self.wrappedValue
},
set: {
let snapshot = self.wrappedValue
self.wrappedValue = $0
execute(snapshot)
}
)
}
}
And the the parent view, not TimerView
you can have something like this
//[...]
TimerView(running: $running.didSet{ value in /* your code or function here*/ })
//[...]
Check my answer here
UPDATE 1
Based on the comment what I understood is that the you want to start/stop the timer from outside the TimerView.
I found a couple ways to do that
First one declare the TimerView as a variable in the parent view
struct ContentView: View {
@State var running: Bool = false
@State var timerView: TimerView? = nil
init() {
timerView = TimerView(running: $running)
}
// rest...
}
But depending on your init it can be a pain so I created a small extension that allows you to reference the view like so
extension View {
func `referenced`<T>(by reference: Binding<T>) -> (Self?){
DispatchQueue.main.async { reference.wrappedValue = self as! T }
return self
}
}
The usage of this extension is rather simple
struct ContentView: View {
@State var running: Bool = false
@State var timerView: TimerView? = nil
var body: some View {
TimerView(running: $running).referenced(by: $timerView)
}
}
Then you can control it using for instance a button Button(action: self.timerView.start()) { Text("Start Timer") }
If tested this code against your TimerView but seems there's something wrong in the stop()
code. I commented the line cancellable!.cancel()
and added a print
instead and everything is working as expected.
Here's the test code that I used in Xcode Playground
I hope this is the answer you're looking for.
How to call a function when a @State variable changes
You may use combineLatest in the main view.
struct MainTimeView: View{
@State private var min : Int = 0
@State private var sec : Int = 0
@State private var myTime: Int = 0
var body: some View {
TimePicker(min: $min, sec: $sec).onReceive(Publishers.CombineLatest(Just(min), Just(sec))) {
self.myTime = $0 * 60 + $1
print(self.myTime)
}
}
}
Execute a method when a variable value changes in Swift
You can simply use a property observer for the variable, labelChange
, and call the function that you want to call inside didSet
(or willSet
if you want to call it before it has been set):
class SharingManager {
var labelChange: String = Model().callElements() {
didSet {
updateLabel()
}
}
static let sharedInstance = SharingManager()
}
This is explained in Property Observers.
I'm not sure why this didn't work when you tried it, but if you are having trouble because the function you are trying to call (updateLabel
) is in a different class, you could add a variable in the SharingManager
class to store the function to call when didSet
has been called, which you would set to updateLabel
in this case.
Edited:
So if you want to edit a label from the ViewController, you would want to have that updateLabel() function in the ViewController class to update the label, but store that function in the singleton class so it can know which function to call:
class SharingManager {
static let sharedInstance = SharingManager()
var updateLabel: (() -> Void)?
var labelChange: String = Model().callElements() {
didSet {
updateLabel?()
}
}
}
and then set it in whichever class that you have the function that you want to be called, like (assuming updateLabel is the function that you want to call):
SharingManager.sharedInstance.updateLabel = updateLabel
Of course, you will want to make sure that the view controller that is responsible for that function still exists, so the singleton class can call the function.
If you need to call different functions depending on which view controller is visible, you might want to consider Key-Value Observing to get notifications whenever the value for certain variables change.
Also, you never want to initialize a view controller like that and then immediately set the IBOutlets of the view controller, since IBOutlets don't get initialized until the its view actually get loaded. You need to use an existing view controller object in some way.
Hope this helps.
Is there a way to call a function when a slider is used in swift?
We can use computable in-inline binding with side-effect for this (and you can remove onChange at all), here is a demo
Slider(value: Binding(get: { noteLength },
set: {
// willSet side-effect here // << here !!
noteLenght = $0
// didSet side-effect here // << here !!
}),
in: 0.1...3.01, step: 0.1)
.onChange(of: noteLength, perform: { value in
defaults.set(value, forKey: "noteLengthEx1")
levelIndex = 4
})
Related Topics
Difference Between String Interpolation and String Initializer in Swift
Why Can't We Use Protocol 'Encodable' as a Type in the Func
Implementing a Function with a Default Parameter Defined in a Protocol
How to Upload Zip Data with Alamofire
How to Avoid Nested Navigation Bars in Swiftui
Showing Cells in Demands in Uicollectionview with Vertical Infinite Scroll
What Is the Practical Use of Nested Functions in Swift
How to Remove Spaces from a String in Swift
"Value of Type 'Authdataresult' Has No Member 'Uid'" Error
Swift 2 Protocol Extension Not Calling Overridden Method Correctly
Adding Local Dependencies in Xcode11 Using Spm
How to Test If Objects Conforming to the Same Protocol Are Identical in Swift Without Casting
Nsurl Found Nil While Unwraping an Optional Value