SwiftUI lets us attach an onChange() modifier to any view, which will run code of our choosing when some state changes in our program. This is important, because we can't always use property observers like didSet with something like @State .
SwiftUI manages the storage of a property that you declare as state. When the value changes, SwiftUI updates the parts of the view hierarchy that depend on the value. Use state as the single source of truth for a given value stored in a view hierarchy.
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)
// }
}
}
}
iOS 14.0+
You can use the onChange(of:perform:)
modifier, like so:
struct ContentView: View {
@State private var isLightOn = false
var body: some View {
Toggle("Light", isOn: $isLightOn)
.onChange(of: isLightOn) { value in
if value {
print("Light is now on!")
} else {
print("Light is now off.")
}
}
}
}
iOS 13.0+
The following as an extension of Binding
, so you can execute a closure whenever the value changes.
extension Binding {
/// When the `Binding`'s `wrappedValue` changes, the given closure is executed.
/// - Parameter closure: Chunk of code to execute whenever the value changes.
/// - Returns: New `Binding`.
func onUpdate(_ closure: @escaping () -> Void) -> Binding<Value> {
Binding(get: {
wrappedValue
}, set: { newValue in
wrappedValue = newValue
closure()
})
}
}
Used like so for example:
struct ContentView: View {
@State private var isLightOn = false
var body: some View {
Toggle("Light", isOn: $isLightOn.onUpdate(printInfo))
}
private func printInfo() {
if isLightOn {
print("Light is now on!")
} else {
print("Light is now off.")
}
}
}
This example doesn't need to use a separate function. You only need a closure.
Here is another option if you have a component that updates a @Binding
. Rather than doing this:
Component(selectedValue: self.$item, ...)
you can do this and have a little greater control:
Component(selectedValue: Binding(
get: { self.item },
set: { (newValue) in
self.item = newValue
// now do whatever you need to do once this has changed
}), ... )
This way you get the benefits of the binding along with the detection of when the Component
has changed the value.
You can use onReceive
:
import Combine
import SwiftUI
struct ContentView: View {
@State private var selection = false
var body: some View {
Toggle("Selection", isOn: $selection)
.onReceive(Just(selection)) { selection in
// print(selection)
}
}
}
In iOS 14 there is now a onChange
modifier you can use like so:
SegmentedControl(selection: $selection) {
ForEach(SectionType.allCases.identified(by: \.self)) { type in
Text(type.rawValue).tag(type)
}
}
.onChange(of: selection) { value in
print("Selection changed to \(selection)")
}
You can use Binding
let textBinding = Binding<String>(
get: { /* get */ },
set: { /* set $0 */ }
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With