3

I am trying to use multiple pickers with dynamic data in SwiftUI (XCode 11.3.1). The app sometimes crashes and sometimes freezes or shows the wrong data in the picker both in the simulator and on a real device running iOS 13.3.1. I tried the suggestions in the answers to this question with no success. What am I doing wrong?

import SwiftUI

struct DbItem : Identifiable {
    var id: Int
    var name: String
}

final class DbStore : ObservableObject {

    let countries: [DbItem] = [DbItem(id: 0, name: "USA"), DbItem(id: 1, name: "France")]
    let citymap:[Int:[DbItem]] = [0:[DbItem(id: 10, name: "New York"), DbItem(id: 11, name: "Los Angeles"), DbItem(id: 12, name: "Dallas"), DbItem(id: 13, name: "Chicago")], 1:[DbItem(id: 20, name: "Paris"), DbItem(id: 21, name: "Nice"), DbItem(id: 22, name: "Lille")]]

    @Published var cities = [DbItem]()
    @Published var country : Int = -1 {
        willSet {
            if newValue >= 0 {
                self.id = UUID()
                DispatchQueue.main.async { [newValue] in
                    self.cities = self.citymap[newValue]!
                }
            }
        }
    }
    @Published var city : Int = -1 {
        didSet {
        }
    }
}

struct ContentView: View {
    @EnvironmentObject private var store: DbStore

    var body: some View {
        NavigationView {
            Form {
                VStack {
                    Picker(selection: $store.country,
                           label: Text("Country: ")
                    ) {
                        ForEach(store.countries) { country in
                            Text(country.name)
                        }
                    }
                    Picker(selection: $store.city,
                           label: Text("City: ")
                    ) {
                        ForEach(store.cities) { city in
                            Text(city.name)
                        }
                    }
                    .disabled(store.country < 0)
                }
            }
        }
    }
}
mect
  • 98
  • 7
  • why you don't follow what is written in the answer from provided link? which part is not clear there? – user3441734 Feb 20 '20 at 22:37
  • Hi @user3441734, first of all thank you for the solution that you provided in the linked question. The difference in my implementation is that the VStack of Pickers is embedded in NavigationView+Form. The PickerStyle then changes from wheel to a list. This is what my design requires. You can reproduce the issue in your example by wrapping the VStack of Pickers in a `NavigationView { Form { ... }}` – mect Feb 21 '20 at 09:56
  • I made required changes, see my answer – user3441734 Feb 21 '20 at 12:34

1 Answers1

2

Using the same code as provided in link mentioned in your question I made some small changes to adopt the code for your needs

struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Header").font(.title)) {
                    Picker(selection: $model.selectedContry, label: Text("Country")){
                        ForEach(0 ..< model.countryNemes.count){ index in
                            Text(self.model.countryNemes[index])
                        }
                    }
                    Picker(selection: $model.selectedCity, label: Text("City")){
                        ForEach(0 ..< model.cityNamesCount){ index in
                            Text(self.model.cityNames[index])
                        }
                        }
                    .id(model.id)
                }
            }.navigationBarTitle("Navigation Title")
        }
    }
}

Please see, that there is no VStack but Section in the Form! The result works as expected. (the rest of code is without any changes). Try the code on real device (due the known "back button" bug in simulator)

enter image description here

In case you have some trouble with the rest of code, here it is

import Foundation
import SwiftUI

struct Country: Identifiable {
    var id: Int = 0
    var name: String
    var cities: [City]
}

struct City: Identifiable {
    var id: Int = 0
    var name: String
}

class Model: ObservableObject {
    let countries: [Country] = [Country(id: 0, name: "USA", cities: [City(id: 0, name: "New York"),City(id: 1, name: "Los Angeles"),City(id: 2, name: "Dallas"),City(id: 3, name: "Chicago")]),Country(id: 1, name: "France", cities: [City(id: 0, name: "Paris")])]

    @Published var selectedContry: Int = 0 {
        willSet {
            print("country changed", newValue, citySelections[newValue] ?? 0)
            selectedCity = citySelections[newValue] ?? 0
            id = UUID()
        }
    }
    @Published var id: UUID = UUID()
    @Published var selectedCity: Int = 0 {
        willSet {
            DispatchQueue.main.async { [newValue] in
                print("city changed", newValue)
                self.citySelections[self.selectedContry] = newValue
            }
        }
    }
    var countryNemes: [String] {
        countries.map { (country) in
            country.name
        }
    }
    var cityNamesCount: Int {
        cityNames.count
    }
    var cityNames: [String] {
        countries[selectedContry].cities.map { (city) in
            city.name
        }
    }

    private var citySelections: [Int: Int] = [:]
}
user3441734
  • 16,722
  • 2
  • 40
  • 59