SwiftUI Picker onChange 或等效物?
SwiftUI Picker onChange or equivalent?
我想在 Picker
更改时更改另一个不相关的 @State
变量,但是没有 onChanged
并且无法将 didSet
放在采摘者 @State
。还有其他方法可以解决这个问题吗?
我用的是分段选择器,也有类似的需求。在尝试了一些事情之后,我只使用了一个同时具有 ObservableObjectPublisher
和 PassthroughSubject
发布者的对象作为选择。这让我对 SwiftUI 感到满意,而且 onReceive()
我也可以做其他事情。
// Selector for the base and radix
Picker("Radix", selection: $base.value) {
Text("Dec").tag(10)
Text("Hex").tag(16)
Text("Oct").tag(8)
}
.pickerStyle(SegmentedPickerStyle())
// receiver for changes in base
.onReceive(base.publisher, perform: { self.setRadices(base: [=10=]) })
base 有一个 objectWillChange
和一个 PassthroughSubject<Int, Never>
发布者,想象中称为发布者。
class Observable<T>: ObservableObject, Identifiable {
let id = UUID()
let objectWillChange = ObservableObjectPublisher()
let publisher = PassthroughSubject<T, Never>()
var value: T {
willSet { objectWillChange.send() }
didSet { publisher.send(value) }
}
init(_ initValue: T) { self.value = initValue }
}
typealias ObservableInt = Observable<Int>
定义 objectWillChange 并不是绝对必要的,但当我写下它时,我喜欢提醒自己它就在那里。
我认为这是更简单的解决方案:
@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 = [=10=]
// TODO: DO YOUR STUFF HERE
// TODO: DO YOUR STUFF HERE
// TODO: DO YOUR STUFF HERE
// USE this if needed to notify parent
self.notifyParentOnChangeIndex = [=10=]
})
return VStack{
Picker(selection: pi, label: Text("Yolo")) {
ForEach(self.yourData.indices) {
Text(self.yourData[[=10=]])
}
}
.pickerStyle(WheelPickerStyle())
.padding()
}
}
iOS14 或更高版本的部署目标
Apple 提供了 View
的内置 onChange
扩展,可以像这样使用:
struct MyPicker: View {
@State private var favoriteColor = 0
var body: some View {
Picker(selection: $favoriteColor, label: Text("Color")) {
Text("Red").tag(0)
Text("Green").tag(1)
}
.onChange(of: favoriteColor) { tag in print("Color tag: \(tag)") }
}
}
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)
}
}
func colorChange(_ tag: Int) {
print("Color tag: \(tag)")
}
}
使用这个助手
extension Binding {
func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value> {
return Binding(
get: { self.wrappedValue },
set: { selection in
self.wrappedValue = selection
handler(selection)
})
}
}
我知道这是一岁 post,但我认为这个解决方案可能会帮助其他需要解决方案的人。希望对其他人有帮助。
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[[=10=]])
}
}.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]
}
}
}
首先,完全感谢 ccwasden 的最佳答案。我不得不稍微修改它以使其对我有用,所以我回答这个问题希望其他人也会发现它有用。
这是我最终得到的结果(在 iOS 14 GM 和 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 \([=10=])") }
}.onChange(of: selection) { _ in
print(selection)
}
}
}
包含“_ in”正是我所需要的。没有它,我收到错误“无法将类型 'Int' 的值转换为预期的参数类型‘()’”
SwiftUI 1 & 2
使用onReceive
和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 \([=10=])")
}
}
.onReceive(Just(selection)) {
print("Selected: \([=10=])")
}
}
}
对于必须同时支持 iOS 13 和 14 的用户,我添加了一个适用于两者的扩展。不要忘记导入 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)
}
}
}
}
用法:
Picker(selection: $selectedIndex, label: Text("Color")) {
Text("Red").tag(0)
Text("Blue").tag(1)
}.onChangeBackwardsCompatible(of: selectedIndex) { (newIndex) in
print("Do something with \(newIndex)")
}
重要说明:如果您正在更改已发布的 属性 在完成块中观察到的对象,此解决方案将导致 [=21= 中的无限循环] 13. 但是,它很容易通过添加检查来修复,如下所示:
.onChangeBackwardsCompatible(of: showSheet, perform: { (shouldShowSheet) in
if shouldShowSheet {
self.router.currentSheet = .chosenSheet
showSheet = false
}
})
iOS 14 和具有关系的 CoreData 实体
我 运行 在尝试绑定到 CoreData 实体时遇到了这个问题,发现以下方法有效:
Picker("Level", selection: $contact.level) {
ForEach(levels) { (level: Level?) in
HStack {
Circle().fill(Color.green)
.frame(width: 8, height: 8)
Text("\(level?.name ?? "Unassigned")")
}
.tag(level)
}
}
.onChange(of: contact.level) { _ in savecontact() }
其中“contact”是与“level”有关系的实体。
Contact
class 是 @ObservedObject var contact: Contact
saveContact
是 do-catch
函数 try viewContext.save()
...
非常重要的问题:我们必须将一些东西传递给 Picker 项目视图(在 ForEach 内部)的“标记”修饰符,以让它“识别”项目并触发选择更改事件。我们传递的值将 return 绑定到具有“选择”选择器的绑定变量。
例如:
Picker(selection: $selected, label: Text("")){
ForEach(data){item in //data's item type must conform Identifiable
HStack{
//item view
}
.tag(item.property)
}
}
.onChange(of: selected, perform: { value in
//handle value of selected here (selected = item.property when user change selection)
})
我想在 Picker
更改时更改另一个不相关的 @State
变量,但是没有 onChanged
并且无法将 didSet
放在采摘者 @State
。还有其他方法可以解决这个问题吗?
我用的是分段选择器,也有类似的需求。在尝试了一些事情之后,我只使用了一个同时具有 ObservableObjectPublisher
和 PassthroughSubject
发布者的对象作为选择。这让我对 SwiftUI 感到满意,而且 onReceive()
我也可以做其他事情。
// Selector for the base and radix
Picker("Radix", selection: $base.value) {
Text("Dec").tag(10)
Text("Hex").tag(16)
Text("Oct").tag(8)
}
.pickerStyle(SegmentedPickerStyle())
// receiver for changes in base
.onReceive(base.publisher, perform: { self.setRadices(base: [=10=]) })
base 有一个 objectWillChange
和一个 PassthroughSubject<Int, Never>
发布者,想象中称为发布者。
class Observable<T>: ObservableObject, Identifiable {
let id = UUID()
let objectWillChange = ObservableObjectPublisher()
let publisher = PassthroughSubject<T, Never>()
var value: T {
willSet { objectWillChange.send() }
didSet { publisher.send(value) }
}
init(_ initValue: T) { self.value = initValue }
}
typealias ObservableInt = Observable<Int>
定义 objectWillChange 并不是绝对必要的,但当我写下它时,我喜欢提醒自己它就在那里。
我认为这是更简单的解决方案:
@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 = [=10=]
// TODO: DO YOUR STUFF HERE
// TODO: DO YOUR STUFF HERE
// TODO: DO YOUR STUFF HERE
// USE this if needed to notify parent
self.notifyParentOnChangeIndex = [=10=]
})
return VStack{
Picker(selection: pi, label: Text("Yolo")) {
ForEach(self.yourData.indices) {
Text(self.yourData[[=10=]])
}
}
.pickerStyle(WheelPickerStyle())
.padding()
}
}
iOS14 或更高版本的部署目标
Apple 提供了 View
的内置 onChange
扩展,可以像这样使用:
struct MyPicker: View {
@State private var favoriteColor = 0
var body: some View {
Picker(selection: $favoriteColor, label: Text("Color")) {
Text("Red").tag(0)
Text("Green").tag(1)
}
.onChange(of: favoriteColor) { tag in print("Color tag: \(tag)") }
}
}
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)
}
}
func colorChange(_ tag: Int) {
print("Color tag: \(tag)")
}
}
使用这个助手
extension Binding {
func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value> {
return Binding(
get: { self.wrappedValue },
set: { selection in
self.wrappedValue = selection
handler(selection)
})
}
}
我知道这是一岁 post,但我认为这个解决方案可能会帮助其他需要解决方案的人。希望对其他人有帮助。
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[[=10=]])
}
}.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]
}
}
}
首先,完全感谢 ccwasden 的最佳答案。我不得不稍微修改它以使其对我有用,所以我回答这个问题希望其他人也会发现它有用。
这是我最终得到的结果(在 iOS 14 GM 和 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 \([=10=])") }
}.onChange(of: selection) { _ in
print(selection)
}
}
}
包含“_ in”正是我所需要的。没有它,我收到错误“无法将类型 'Int' 的值转换为预期的参数类型‘()’”
SwiftUI 1 & 2
使用onReceive
和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 \([=10=])")
}
}
.onReceive(Just(selection)) {
print("Selected: \([=10=])")
}
}
}
对于必须同时支持 iOS 13 和 14 的用户,我添加了一个适用于两者的扩展。不要忘记导入 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)
}
}
}
}
用法:
Picker(selection: $selectedIndex, label: Text("Color")) {
Text("Red").tag(0)
Text("Blue").tag(1)
}.onChangeBackwardsCompatible(of: selectedIndex) { (newIndex) in
print("Do something with \(newIndex)")
}
重要说明:如果您正在更改已发布的 属性 在完成块中观察到的对象,此解决方案将导致 [=21= 中的无限循环] 13. 但是,它很容易通过添加检查来修复,如下所示:
.onChangeBackwardsCompatible(of: showSheet, perform: { (shouldShowSheet) in
if shouldShowSheet {
self.router.currentSheet = .chosenSheet
showSheet = false
}
})
iOS 14 和具有关系的 CoreData 实体
我 运行 在尝试绑定到 CoreData 实体时遇到了这个问题,发现以下方法有效:
Picker("Level", selection: $contact.level) {
ForEach(levels) { (level: Level?) in
HStack {
Circle().fill(Color.green)
.frame(width: 8, height: 8)
Text("\(level?.name ?? "Unassigned")")
}
.tag(level)
}
}
.onChange(of: contact.level) { _ in savecontact() }
其中“contact”是与“level”有关系的实体。
Contact
class 是 @ObservedObject var contact: Contact
saveContact
是 do-catch
函数 try viewContext.save()
...
非常重要的问题:我们必须将一些东西传递给 Picker 项目视图(在 ForEach 内部)的“标记”修饰符,以让它“识别”项目并触发选择更改事件。我们传递的值将 return 绑定到具有“选择”选择器的绑定变量。
例如:
Picker(selection: $selected, label: Text("")){
ForEach(data){item in //data's item type must conform Identifiable
HStack{
//item view
}
.tag(item.property)
}
}
.onChange(of: selected, perform: { value in
//handle value of selected here (selected = item.property when user change selection)
})