0

Im currently learning SwiftUI and I'm having trouble getting my code to print the call from the API

NetworkManager.swift

class NetworkManager: ObservableObject {

    @Published var allCountries = Countries()

    func fetchAllCountries() {
        if let url = URL(string: K.url) {
            print(url)
            let session = URLSession(configuration: .default)
            let task = session.dataTask(with: url) { (data, response, error) in
                if error == nil {
                    let decoder = JSONDecoder()
                    if let countriesData = data {
                        do {
                            let countries = try decoder.decode(Countries.self, from: countriesData)
                            DispatchQueue.main.async {
                                self.allCountries = countries
                            }
                        } catch {
                            print(error)
                        }
                    }
                }
            }
            task.resume()
        }
    }
}

If i print allCountries I can see each item

But on my Main view when I try to print(self.networkManager[5].name) I get an index out of range error

ContentView.swift

import SwiftUI

struct ContentView: View {

    @ObservedObject var networkManager = NetworkManager()

    var body: some View {
        Text("Hello, World!")
            .onAppear {
                self.networkManager.fetchAllCountries()
                print(self.networkManager.allCountries[5].name)
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Can anyone help point out where I'm going wrong?

2 Answers 2

0

the problem is to do with the asynchronous fetchAllCountries. You need to wait till it is finished before trying to use the results. Try something like this;

class NetworkManager: ObservableObject {

@Published var allCountries = Countries()

func fetchAllCountries(handler: @escaping () -> Void) {
    if let url = URL(string: K.url) {
        print(url)
        let session = URLSession(configuration: .default)
        let task = session.dataTask(with: url) { (data, response, error) in
            if error == nil {
                let decoder = JSONDecoder()
                if let countriesData = data {
                    do {
                        let countries = try decoder.decode(Countries.self, from: countriesData)
                        DispatchQueue.main.async {
                            self.allCountries = countries
                            return handler()
                        }
                    } catch {
                        print(error)
                    }
                }
            }
            handler()
        }
        task.resume()
    }
}
}

struct ContentView: View {

@ObservedObject var networkManager = NetworkManager()

var body: some View {
    Text("Hello, World!")
        .onAppear {
            self.networkManager.fetchAllCountries() {
                print(self.networkManager.allCountries[5].name)
            }
    }
}
}
Sign up to request clarification or add additional context in comments.

Comments

0

You try to access .allCountries too early, because .fetchAllCountries is a long (relatively) asynchronous operation.

The solution is to add explicit observer to fetched countries, like below

    var body: some View {
        Text("Hello, World!")
            .onAppear {
                self.networkManager.fetchAllCountries()
            }
            .onReceive(networkManager.$allCountries) { countries in
                 print(countries[5].name) // assuming Countries support subscript
            }
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.