Search code examples
jsonswiftuixcode11pickerobservedobject

ObservedObject, derived from Json, not loading into Picker, but loads in List. In SwiftUI, Xcode 11


I'm new to mobile development, and in the process of learning SwiftUI.

I've been struggling to figure out what's wrong with my picker. I am successfully returning the data from my URLSession, adding it to my model. I can confirm this by adding my @ObservedObject to a List, which returns all of the items. Putting the same @ObservedObject into a picker returns an empty picker for some reason. Any help would be appreciated. Thanks.

Here's my view with the Picker(). When run, the Picker is empty. I can comment out the Picker(), leaving just the ForEach() with Text(), and the text appears.

import Foundation
import Combine
import SwiftUI

struct ContentView: View {

@ObservedObject var countries = CulturesViewModel()
@State private var selectedCountries = 0

    var body: some View {

        VStack {

            //loop through country array and add them to picker
            Picker(selection: $selectedCountries, label: Text("Select Your Country")) {
                ForEach(0 ..< countries.cultures.count, id: \.self) { post in
                    Text(self.countries.cultures[post].Culture).tag(post)
               }
            }.labelsHidden()     
        }

    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Here's my ViewModel. It set's the @Published variable to the results of the JSON request in the WebService(). If I hard-code the @Published variable to the value that's begin returned, the Picker works.

import Foundation
import Combine
import SwiftUI

class CulturesViewModel: ObservableObject {
    init() {
        fetchCultures()
    }

    @Published var cultures = [Culture](){
        didSet {
            didChange.send(self)
        }
    }

    private func fetchCultures(){
        WebService().GetCultures {
            self.cultures = $0
        }
    }

    let didChange = PassthroughSubject<CulturesViewModel, Never>()
}

Here's my WebService(). Unfortunately, I'm unable to share the JSON url, I've added in the json that's returned.

import Foundation
import SwiftUI
import Combine

class WebService {

    func GetCultures(completion: @escaping([Culture]) ->()) {

        guard let url = URL("")

        [
            {
                "CultureId": 0,
                "Culture": "Select Your Country"
            },
            {
                "CultureId": 1078,
                "Culture": "English (United States)"
            },
            {
                "CultureId": 6071,
                "Culture": "English (Canada)"
            }
        ]

        URLSession.shared.dataTask(with: url) { (data,_,_) in
            do {
                if let data = data {
                    let culturesList = try JSONDecoder().decode([Culture].self, from: data)
                    DispatchQueue.main.async {
                        completion(culturesList)
                    }
                } else {
                    DispatchQueue.main.async {
                        completion([])
                    }
                }

            } catch {
                print(error)
                DispatchQueue.main.async {
                    completion([])
                }
            }
        }.resume()
    }
}

Lastly, here's my Model.

import Foundation

struct Culture: Codable, Identifiable {
    var id = UUID()
    var CultureId: Int
    var Culture: String
}

Solution

  • The work around to make the picker refresh is to add a unique id. Refreshing (or) reloading the countries, will create a new UUID for the picker items. This will force the picker to refresh. I've modified your code to include an id.

    //loop through country array and add them to picker
    Picker(selection: $selectedCountries, label: Text("Select Your Country")) {
        ForEach(0 ..< countries.cultures.count, id: \.self) { post in
            Text(self.countries.cultures[post].Culture).tag(post)
       }
    }.labelsHidden()
    .id(UUID())
    

    This seems to be a known issue with the picker.