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

5kgi1eie  于 2023-01-06  发布在  iOS
关注(0)|答案(5)|浏览(129)

我刚开始学习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!

**默认选择的第一段:**x1c 0d1x
更改段:

将段改回第一段:

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

4uqofj5v

4uqofj5v1#

Nick Polychronakis在这个分叉中解决了它: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)
zqdjd7g9

zqdjd7g92#

我不确定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)
                    }
                }
            }
        }
    }
}

**注:**必须使用不同的状态变量来跟踪温度和长度选择。

mccptt67

mccptt673#

结合前面两个答案:
内容视图

...

    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

q35jwt9p

q35jwt9p4#

仅供参考:以上答案不适用于SwiftUI中的Wheelpickerstyle。单位计数将保持初始值,因此如果您从温度开始,然后切换到长度,则将丢失长度数组的最后两个值。如果您选择相反的方式,则应用将崩溃并出现越界。
我花了很长时间才找到一个解决方案。看起来这是Wheelpickerstyle中的一个bug。解决方法是更新picker的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()  
    }  
}
qxsslcnc

qxsslcnc5#

只需在ForEach(Picker)中为文本设置标记即可。

.tag(String?.some(item))

相关问题