簡體   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