Avba
Avba

Reputation: 15266

sectioned pickerview chooses all rows in sections with same index

I am trying to create a PickerView which has sections

each item conforms to "id" and I am tagging the Text with the item's id (which is unique and I verified that there are no clashes)

The PickerView seems to disregard the tags I am assigning and chooses all rows with the same corresponding index for each section

I also tried tagging each item with a random UUID to check the behavior and it seems to continue

struct ExperimentPickerView: View {
    @StateObject var localExperiment = RunningExperiment.active
    @StateObject var experiments = RemoteExperiments.instance
    @State var picked : Int = -1
    var body: some View {
        Picker("active", selection: $picked) {
            ForEach(Array(experiments.experiments.enumerated()), id: \.offset) { i,experiment in
                Section(header: Text("\(experiment.name)")) {
                    ForEach(Array(experiment.variations.enumerated()), id: \.offset) { j,variation in
//                        Text("\(variation.name) \(variation.id)").tag(variation.id)
                        Text("\(variation.name) \(variation.id)").tag(UUID().description)
                        
                    }
                }
            }
        }.id(picked).onReceive([self.picked].publisher.first()) { (value) in
            print(value) // prints the row number and not the id of the element
            
        }
    }
}


struct Experiment : Codable, Equatable, Identifiable {
    var id: Int {
        var hasher = Hasher()
        name.hash(into: &hasher)
        variations.hash(into: &hasher)
        return hasher.finalize()
    }
    let name : String
    let variations: [Variation]
    enum CodingKeys: String, CodingKey {
        case name
        case variations
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let n = try container.decode(String.self, forKey: .name)
        name = n
        let a = try container.decode(AnyCodable.self, forKey: .variations)
        print(a)
        let b = a.value as! [[String:Any]]
        var vars = [Variation]()
        for v in b {
            if let na = v["name"], let nna = na as? String {
                vars.append(Variation(name: nna, experiment: n))
            }
        }
        variations = vars
        
        
    }
}

struct Variation: Codable, Equatable, Identifiable, Hashable {
    var id: Int {
        var hasher = Hasher()
        name.hash(into: &hasher)
        experiment.hash(into: &hasher)
        let hashValue = hasher.finalize()
        return hashValue
    }
    
    let name: String
    var experiment: String
    
    enum CodingKeys: String, CodingKey {
        case name, experiment
    }
}

Upvotes: 0

Views: 67

Answers (1)

Asperi
Asperi

Reputation: 257789

The tag should be same type as selection, because it is used to match picked row. And id should not be modified in this scenario, because it reinitialises picker completely.

Provided code is not testable so here is just a demo of solution (simplified replication of your scenario).

Tested with Xcode 12.1 / iOS 14.1.

struct Pair: Hashable {
    var section = ""
    var row = -1
}

struct ExperimentPickerView: View {
    var sections = ["A", "B", "C"]
    var rows = Array(0..<5)

    @State private var picked = Pair(section: "B", row: 2) // << here !! (initialised demo)

    var body: some View {
        Picker("active", selection: $picked) {
            ForEach(sections, id: \.self) { section in
                Section(header: Text("\(section)").bold()) {
                    ForEach(rows, id: \.self) { i in
                        Text("\(section) \(i)")
                           .tag(Pair(section: section, row: i))   // << match !!
                    }
                }
            }
        }.onReceive([self.picked].publisher.first()) { (value) in
            print(value)
        }
    }
}

demo1

demo2

Upvotes: 0

Related Questions