3
votes

I'm trying to set a published variable named allCountries. In my DispatchQueue, it's being set and is actually printing out the correct information to the console. In my content view, nothing is in the array. In the init function on the content view, I'm calling the the function that will fetch the data and set the variable but when I print to the console, the variable is blank. Can someone tell me what I'm doing wrong?

Here is where I'm fetching the data

class GetCountries: ObservableObject {

@Published var allCountries = [Countries]()

func getAllPosts() {

    guard let url = URL(string: "apiURL")
        else {
            fatalError("URL does not work!")
    }

    URLSession.shared.dataTask(with: url) { data, _, _ in
        do {
            if let data = data {
                let posts = try JSONDecoder().decode([Countries].self, from: data)


                DispatchQueue.main.async {

                    self.allCountries = posts
                    print(self.allCountries)

                }
            } else  {
                DispatchQueue.main.async {
                    print("didn't work")
                }
            }
        }
        catch {
            DispatchQueue.main.async {
                print(error.localizedDescription)
            }
        }

    }.resume()
}

}

Here's my content view

struct ContentView: View {

var countries = ["Select Your Country", "Canada", "Mexico", "United States"]

@ObservedObject var countries2 = GetCountries()

@State private var selectedCountries = 0

init() {
    GetCountries().getAllPosts()
    print(countries2.allCountries)

}

var body: some View {
    NavigationView {

        ZStack {

            VStack {
                Text("\(countries2.allCountries.count)")}}}
2

2 Answers

1
votes

The problem is happening in the init of your ContentView. You're calling the TYPE's function getAllPosts, but then you're trying to access the data of a new INSTANCE of that Type countries2. Your countries2.allCountries instance property really has no relationship to the GetCountries.allCountries type property.

Does that even compile? Usually you need to define a class's functions and properties as static if you want to call them on the Type itself and not on an instance of it.

Maybe it's weirdness because you're trying to do this in the ContentView's init, rather than after it has initialized.

There's probably two things you need to do to fix this.

Step one would be to let your custom initializer actually initialize countries2.

And step two would be to call countries2.getAllPosts within that custom initializer, which will populate the instance property you're trying to access in countries2.allCountries.

Try this:

struct ContentView: View {

var countries = ["Select Your Country", "Canada", "Mexico", "United States"]

// You'll initialize this in the custom init
@ObservedObject var countries2: GetCountries

@State private var selectedCountries = 0

init() {
    countries2 = GetCountries()
    countries2.getAllPosts()
    print(countries2.allCountries)

}

var body: some View {
    NavigationView {

        ZStack {

            VStack {
                Text("\(countries2.allCountries.count)")}}}

But I'm not sure if you're going to run into problems with the async call within the ContentView initializer. It might be better practice to move that into a function which is called when the view is loaded, rather than when the SwiftUI struct is initialized.

To do that you move your networking calls into a function of ContentView (instead of its init), then use an .onAppear modifier to your view, which will call a function when the NavigationView appears. (Now that we're not using a custom initializer, we'll need to go back to initializing the countries2 property.)

struct ContentView: View {

    var countries = ["Select Your Country", "Canada", "Mexico", "United States"]

    // We're initializing countries2 again
    @ObservedObject var countries2 = GetCountries()

    @State private var selectedCountries = 0

    // This is the function that will get your country data
    func getTheCountries() {
        countries2.getAllPosts()
        print(countries2.allCountries)

    }

    var body: some View {
        NavigationView {
            ZStack {
                VStack {
                    Text("\(countries2.allCountries.count)")
                }
            }
        }
        .onAppear(perform: getTheCountries) // Execute this when the NavigationView appears
    }
}

Note with this approach. Depending on your UI architecture, every time NavigationView appears, it will call getTheCountries. If you make NavigationLink calls to new views, then it may get reloaded loaded if you go back to this first page. But you can easily add a check within getTheCountries to see if the work is actually required.

0
votes

It should be used one same instance to initiate request & read results, so here is fixed variant:

struct ContentView: View {

    var countries = ["Select Your Country", "Canada", "Mexico", "United States"]

    @ObservedObject var countries2 = GetCountries()   // member
    @State private var selectedCountries = 0

    init() {
        // GetCountries().getAllPosts()      // [x] local variable
        // print(countries2.allCountries)
    }

    var body: some View {
        NavigationView {
            ZStack {
                VStack {
                    Text("\(countries2.allCountries.count)")
                }
            }
        }
        .onAppear { self.countries2.getAllPosts() } // << here !!
    }
}