【问题标题】:SwiftUI : Picker does not update correctly when changing datasourceSwiftUI:更改数据源时选择器无法正确更新
【发布时间】: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!

默认选择第一段:

更改细分:

将片段改回第一个:

任何帮助或建议将不胜感激。

【问题讨论】:

    标签: ios swift swiftui ios13 picker


    【解决方案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()  
          }  
      }  
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-11-16
        • 1970-01-01
        • 1970-01-01
        • 2012-09-28
        • 1970-01-01
        • 2015-09-01
        • 2019-08-08
        相关资源
        最近更新 更多