SwiftUI:更改数据源时选择器无法正确更新
Posted
技术标签:
【中文标题】SwiftUI:更改数据源时选择器无法正确更新【英文标题】:SwiftUI : Picker does not update correctly when changing datasource 【发布时间】:2019-10-12 09:35:53 【问题描述】:我刚开始学习 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!
默认选择第一段:
更改细分:
将片段改回第一个:
任何帮助或建议将不胜感激。
【问题讨论】:
【参考方案1】:Nick Polychronakis 在这个 fork 中解决了这个问题: https://github.com/nickpolychronakis/100DaysOfSwiftUI/tree/master/UnitCoverter
解决方案是将 .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)
【讨论】:
我只有 1 个选择器,我发现如果您添加id(unit)
,您将无法再滚动选择器,因为它会弹回零项。但是,如果您将 self.$unit 设置为 Picker 的选择,则它可以工作。我仍然认为这是一个错误,但这是一个明显的解决方法。
我明白了,tnx 分享! SwiftUI 真正棘手的一件事是,您永远无法确定它是否是一个错误,或者它应该像那样工作 XD【参考方案2】:
我不确定为什么 SwiftUI 会这样,对我来说似乎是一个错误(如果我错了,请纠正我)。我只能建议为温度和长度添加单独的选择器,并根据当前选择的类型隐藏它们。为了代码可重用性,我已将选择器添加到另一个文件中。
MyCustomPicker
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)
注意:您必须使用不同的状态变量来跟踪温度和长度选择。
【讨论】:
感谢您的回复 :) 但是我发现@roblack 的解决方案如果我有更多类型而不是 2 种,则更容易和方便。【参考方案3】:结合之前的两个答案:
内容视图
...
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)
见https://github.com/hugofalkman/UnitConverter.git
【讨论】:
【参考方案4】:以上答案仅供参考,不适用于 SwiftUI 中的 Wheelpickerstyle。单位计数将保持在初始值,因此如果您从温度开始,然后切换到长度,您将丢失长度数组的最后两个值。如果你走另一条路,你的应用程序将崩溃并越界。
我花了很长时间才找到解决方案。这似乎是 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()
【讨论】:
以上是关于SwiftUI:更改数据源时选择器无法正确更新的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI 选择器在 ObservedObject 公共变量更新期间滚动时跳转