[英]SwiftUI : Picker does not update correctly when changing datasource
我刚开始学习 SwiftUI 并且卡在了某个地方!
我试图在更改另一个段的值时更改段样式的选择器数据源。 但不知何故,它没有按预期工作。 否则我可能编码错误? 有人能弄清楚吗?
这是我的一段代码:
import SwiftUI
struct ContentView: View {
@State var selectedType = 0
@State var inputUnit = 0
@State var outputUnit = 1
let arrTypes = ["Temperature", "Length"]
var arrData: [String] {
switch self.selectedType {
case 0:
return ["Celsius", "Fahrenheit", "Kelvin"] //Temperature
case 1:
return ["meters", "kilometers", "feet", "yards", "miles"] //Length
default:
return ["Celsius", "Fahrenheit", "Kelvin"]
}
}
var body: some View {
NavigationView{
Form
{
Section(header: Text("Choose type"))
{
Picker("Convert", selection: $selectedType) {
ForEach(0 ..< 2, id: \.self)
{ i in
Text(self.arrTypes[i])
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("From"))
{
Picker("", selection: $inputUnit) {
ForEach(0 ..< arrData.count, id: \.self)
{
Text(self.arrData[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("To"))
{
Picker("", selection: $outputUnit) {
ForEach(0 ..< arrData.count, id: \.self)
{
Text(self.arrData[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
}
}
}
}
}
当我将段从Length
更改回Temperature
时,它会以某种方式合并数组。 我尝试调试并在日志中打印arrData
计数,然后它打印出正确的结果但不更新 UI!
更改段:
将段改回第一段:
任何帮助或建议将不胜感激。
Nick Polychronakis 在这个分支中解决了它: https://github.com/nickpolychronakis/100DaysOfSwiftUI/tree/master/UnitCoverter
解决方案是将 add.id(:identifier:) 添加到您的选择器中,使其独一无二。
可观察变量:
@State var unit = 0
主要选择器:
Picker("Length", selection: $unit) {
ForEach(0 ..< inputUnitTypes.count) {
Text("\(self.inputUnitTypes[$0].description)")
}
}
.pickerStyle(SegmentedPickerStyle())
二级选择器之一,其内容由单元变量确定。
Picker("Length", selection: $inputUnit) {
ForEach(0 ..< selected.count) {
Text("\(self.selected[$0].description)")
}
}
.id(unit)
我不确定为什么 SwiftUI 会这样,对我来说似乎是一个错误(如果我错了,请纠正我)。 我只能建议为温度和长度添加单独的选择器,并根据当前选择的类型隐藏它们。 为了代码可重用性,我已将选择器添加到另一个文件中。
我的自定义选择器
struct MyCustomPicker: View {
var pickerData: [String]
@Binding var binding: Int
var body: some View {
Picker("Convert", selection: $binding) {
ForEach(0 ..< pickerData.count, id: \.self)
{ i in
Text(self.pickerData[i])
}
}
.pickerStyle(SegmentedPickerStyle())
}
}
内容视图
struct ContentView: View {
@State var selectedType = 0
@State var inputTempUnit = 0
@State var outputTempUnit = 1
@State var inputLenUnit = 0
@State var outputLenUnit = 1
let arrTypes = ["Temperature", "Length"]
let tempData = ["Celsius", "Fahrenheit", "Kelvin"]
let lenData = ["meters", "kilometers", "feet", "yards", "miles"]
var body: some View {
NavigationView {
Form {
Section(header: Text("Choose type")) {
MyCustomPicker(pickerData: arrTypes, binding: $selectedType)
}
Section(header: Text("From")) {
if selectedType == 0 {
MyCustomPicker(pickerData: tempData, binding: $inputTempUnit)
} else {
MyCustomPicker(pickerData: lenData, binding: $inputLenUnit)
}
}
Section(header: Text("To")) {
if selectedType == 0 {
MyCustomPicker(pickerData: tempData, binding: $outputTempUnit)
} else {
MyCustomPicker(pickerData: lenData, binding: $outputLenUnit)
}
}
}
}
}
}
注意:您必须使用不同的 state 变量来跟踪温度和长度选择。
结合前面的两个答案:
内容视图
...
var units: [String] {
symbols[unitType]
}
...
Section(header: Text("Unit Type")) {
UnitPicker(units: unitTypes, unit: $unitType)
}
Section(header: Text("From Unit")) {
UnitPicker(units: units, unit: $inputUnit)
.id(unitType)
}
Section(header: Text("To Unit")) {
UnitPicker(units: units, unit: $outputUnit)
.id(unitType)
}
...
单位选择器
struct UnitPicker: View {
var units: [String]
@Binding var unit: Int
var body: some View {
Picker("", selection: $unit) {
ForEach(units.indices, id: \.self) { index in
Text(self.units[index]).tag(index)
}
}
.pickerStyle(SegmentedPickerStyle())
.font(.largeTitle)
}
}
仅供参考,以上答案不适用于 SwiftUI 中的 Wheelpickerstyle。 单位计数将保持在初始值,因此如果您从温度开始,然后切换到长度,您将丢失长度数组的最后两个值。 如果您以另一种方式 go ,您的应用程序将因越界而崩溃。
我花了很长时间才找到解决方案。 这似乎是 Wheelpickerstyle 中的一个错误。 解决方法是更新选取器的 ID,提示它重新加载所有数据源。 我在下面提供了一个示例。
import SwiftUI
// Data
struct Item: Identifiable {
var id = UUID()
var category:String
var item:String
}
let myCategories:[String] = ["Category 1","Category 2"]
let myItems:[Item] = [
Item(category: "Category 1", item: "Item 1.1"),
Item(category: "Category 1", item: "Item 1.2"),
Item(category: "Category 2", item: "Item 2.1"),
Item(category: "Category 2", item: "Item 2.2"),
Item(category: "Category 2", item: "Item 2.3"),
Item(category: "Category 2", item: "Item 2.4"),
]
// Factory
class MyObject: ObservableObject {
// Category picker variables
@Published var selectedCategory:String = myCategories[0]
@Published var selectedCategoryItems:[Item] = []
@Published var selectedCategoryInt:Int = 0 {
didSet {
selectCategoryActions(selectedCategoryInt)
}
}
// Item picker variables
@Published var selectedItem:Item = myItems[0]
@Published var selectedItemInt:Int = 0 {
didSet {
selectedItem = selectedCategoryItems[selectedItemInt]
}
}
@Published var pickerId:Int = 0
// Initial category selection
init() {
selectCategoryActions(selectedCategoryInt)
}
// Actions when selecting a new category
func selectCategoryActions(_ selectedCategoryInt:Int) {
selectedCategory = myCategories[selectedCategoryInt]
// Get items in category
selectedCategoryItems = myItems.filter{ $0.category.contains(selectedCategory)}
// Select initial item in category
let selectedItemIntWrapped:Int? = myItems.firstIndex { $0.category == selectedCategory }
if let selectedItemInt = selectedItemIntWrapped {
self.selectedItem = myItems[selectedItemInt]
}
self.pickerId += 1 // Hack to change ID of picker. ID is updated to force refresh
}
}
// View
struct ContentView: View {
@ObservedObject var myObject = MyObject()
var body: some View {
VStack(spacing: 10) {
Section(header: Text("Observable Object")) {
Text("Selected category: \(myObject.selectedCategory)")
Text("Items in category: \(myObject.selectedCategoryItems.count)")
Text("PickerId updated to force refresh \(myObject.pickerId)")
Text("Selected item: \(myObject.selectedItem.item)")
Picker(selection: self.$myObject.selectedCategoryInt, label: Text("Select category")) {
ForEach(0 ..< myCategories.count, id: \.self) {
Text("\(myCategories[$0])")
}
}.labelsHidden()
Picker(selection: self.$myObject.selectedItemInt, label: Text("Select object item")) {
ForEach(0 ..< self.myObject.selectedCategoryItems.count, id: \.self) {
Text("\(self.myObject.selectedCategoryItems[$0].item)")
}
}
.labelsHidden()
.id(myObject.pickerId) // Hack to get picker to reload data. ID is updated to force refresh.
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
只需在 ForEach (Picker) 中将标签设置为您的文本。
.tag(String?.some(item))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.