I want to change some other unrelated @State
variable when a Picker
gets changed but there is no onChanged
and it\'s not possible to p
I think this is simpler solution:
@State private var pickerIndex = 0
var yourData = ["Item 1", "Item 2", "Item 3"]
// USE this if needed to notify parent
@Binding var notifyParentOnChangeIndex: Int
var body: some View {
let pi = Binding<Int>(get: {
return self.pickerIndex
}, set: {
self.pickerIndex = $0
// TODO: DO YOUR STUFF HERE
// TODO: DO YOUR STUFF HERE
// TODO: DO YOUR STUFF HERE
// USE this if needed to notify parent
self.notifyParentOnChangeIndex = $0
})
return VStack{
Picker(selection: pi, label: Text("Yolo")) {
ForEach(self.yourData.indices) {
Text(self.yourData[$0])
}
}
.pickerStyle(WheelPickerStyle())
.padding()
}
}
First of all, full credit to ccwasden for the best answer. I had to modify it slightly to make it work for me, so I'm answering this question hoping someone else will find it useful as well.
Here's what I ended up with (tested on iOS 14 GM with Xcode 12 GM)
struct SwiftUIView: View {
@State private var selection = 0
var body: some View {
Picker(selection: $selection, label: Text("Some Label")) {
ForEach(0 ..< 5) {
Text("Number \($0)") }
}.onChange(of: selection) { _ in
print(selection)
}
}
}
The inclusion of the "_ in" was what I needed. Without it, I got the error "Cannot convert value of type 'Int' to expected argument type '()'"
Use onReceive
and Just
:
import Combine
import SwiftUI
struct ContentView: View {
@State private var selection = 0
var body: some View {
Picker("Some Label", selection: $selection) {
ForEach(0 ..< 5, id: \.self) {
Text("Number \($0)")
}
}
.onReceive(Just(selection)) {
print("Selected: \($0)")
}
}
}
Here is what I just decided to use... (deployment target iOS 13)
struct MyPicker: View {
@State private var favoriteColor = 0
var body: some View {
Picker(selection: $favoriteColor.onChange(colorChange), label: Text("Color")) {
Text("Red").tag(0)
Text("Green").tag(1)
Text("Blue").tag(2)
}
}
func colorChange(_ tag: Int) {
print("Color tag: \(tag)")
}
}
Using this helper
extension Binding {
func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value> {
return Binding(
get: { self.wrappedValue },
set: { selection in
self.wrappedValue = selection
handler(selection)
})
}
}
EDIT:
If your deployment target is set to iOS 14 or higher -- Apple has provided a built in onChange
extension to View
, which can be used like this instead (Thanks @Jeremy):
Picker(selection: $favoriteColor, label: Text("Color")) {
// ..
}
.onChange(of: favoriteColor) { print("Color tag: \($0)") }
I know this is a year old post, but I thought this solution might help others that stop by for a visit in need of a solution. Hope it helps someone else.
import Foundation
import SwiftUI
struct MeasurementUnitView: View {
@State var selectedIndex = unitTypes.firstIndex(of: UserDefaults.standard.string(forKey: "Unit")!)!
var userSettings: UserSettings
var body: some View {
VStack {
Spacer(minLength: 15)
Form {
Section {
Picker(selection: self.$selectedIndex, label: Text("Current UnitType")) {
ForEach(0..<unitTypes.count, id: \.self) {
Text(unitTypes[$0])
}
}.onReceive([self.selectedIndex].publisher.first()) { (value) in
self.savePick()
}
.navigationBarTitle("Change Unit Type", displayMode: .inline)
}
}
}
}
func savePick() {
if (userSettings.unit != unitTypes[selectedIndex]) {
userSettings.unit = unitTypes[selectedIndex]
}
}
}
For people that have to support both iOS 13 and 14, I added an extension which works for both. Don't forget to import Combine.
Extension View {
@ViewBuilder func onChangeBackwardsCompatible<T: Equatable>(of value: T, perform completion: @escaping (T) -> Void) -> some View {
if #available(iOS 14.0, *) {
self.onChange(of: value, perform: completion)
} else {
self.onReceive([value].publisher.first()) { (value) in
completion(value)
}
}
}
}
Usage:
Picker(selection: $selectedIndex, label: Text("Color")) {
Text("Red").tag(0)
Text("Blue").tag(1)
}.onChangeBackwardsCompatible(of: selectedIndex) { (newIndex) in
print("Do something with \(newIndex)")
}