繁体   English   中英

SwiftUI:更改数据源时选择器未正确更新

[英]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)
    }
}

https://github.com/hugofalkman/UnitConverter.git

仅供参考,以上答案不适用于 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM