Search code examples
jsonapiswiftuiurlsessionfetchrequest

SwiftUI Fetching JSON API Data from api.city.bik


I am having issues trying to get this data. I heard there is a trick. Can anyone create a simple call to view the data from this api? Would truly appreciate it. Been trying for a week. I cant for the life of me get this simple api call to work.

http://api.citybik.es/v2/networks

Model.swift

import Foundation

// MARK: - Welcome
struct Dataset: Codable {
    let networks: [Network]
}

// MARK: - Network
struct Network: Codable {
    let company: [String]
    let href, id: String
    let location: Location
    let name: String
}

// MARK: - Location
struct Location: Codable {
    let city, country: String
    let latitude, longitude: Double
}

Contentview.swift

import SwiftUI

struct ContentView: View {
    
    @State var results = [Network]()
    
    func loadData() {
        guard let url = URL(string: "http://api.citybik.es/v2/networks") else {
            print("Your API end point is Invalid")
            return
        }
        let request = URLRequest(url: url)

        URLSession.shared.dataTask(with: request) { data, response, error in
            if let data = data {
                if let response = try? JSONDecoder().decode([Network].self, from: data) {
                    DispatchQueue.main.async {
                        self.results = response
                    }
                    return
                }
            }
        }.resume()
    }
    
    var body: some View {
        List(results, id: \.name) { item in
            VStack(alignment: .leading) {
                Text("\(item.name)")
            }
        }.onAppear(perform: loadData)
    }
    
    
}

Solution

  • copy the whole of the json from : "https://api.citybik.es/v2/networks" into "https://app.quicktype.io/" and get the (correct) swift data structures from that. Rename "Welcome" to "Response" and use that in your code.

    use: "https://api.citybik.es/v2/networks" note the https.

    EDIT: In your code:

    struct ContentView: View {
        @State var networks = [Network]()
        
        var body: some View {
            List(networks, id: \.id) { network in
                VStack {
                    Text(network.name)
                    Text(network.location.city)
                }
            }.onAppear(perform: loadData)
        }
        
        func loadData() {
            guard let url = URL(string: "https://api.citybik.es/v2/networks") else {
                print("Your API end point is Invalid")
                return
            }
            let request = URLRequest(url: url)
            URLSession.shared.dataTask(with: request) { data, response, error in
                if let data = data {
                    if let response = try? JSONDecoder().decode(Response.self, from: data) {
                        DispatchQueue.main.async {
                            self.networks = response.networks
                        }
                        return
                    }
                }
            }.resume()
        }
    } 
    

    Once you have all the data structures, and if you are using Swift 5.5 for ios 15 or macos 12, you can use something like this:

    struct ContentView: View {
        @State var networks = [Network]()
        
        var body: some View {
            List(networks, id: \.id) { network in
                VStack {
                    Text(network.name)
                    Text(network.location.city)
                }
            }
            .task {
                let response: Response? = await fetchNetworks()
                if let resp = response {
                    networks = resp.networks
                }
            }
        }
        
        func fetchNetworks<T: Decodable>() async -> T? {
            let url = URL(string: "https://api.citybik.es/v2/networks")!
            let request = URLRequest(url: url)
            do {
                let (data, response) = try await URLSession.shared.data(for: request)
                guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
                    // throw URLError(.badServerResponse)   //  todo
                    print(URLError(.badServerResponse))
                    return nil
                }
                let results = try JSONDecoder().decode(T.self, from: data)
                return results
            }
            catch {
                return nil
            }
        }
    }