Home > Back-end >  Swift adding to an array using an API request
Swift adding to an array using an API request

Time:07-12

I'm trying to create a list of data collected from an API and I'm stuck as to how to append the API results to the list. I know it's a bit of a dumb one but I was hoping somebody could eye over the code and help me work out how to implement it

The function which has dummy data inside:

private func getLocations() -> [Crime] {
    let locationArray : [Crime] = [
    Crime(latitude: 51.4586, longitude: -2.5936),
    Crime(latitude: 51.4599, longitude: -2.5939)
    ]
    return locationArray
}

My API call function:

struct Crime: Codable, Identifiable{
    var id = UUID()
    var latitude: Double
    var longitude: Double
}

func getAPI(completion: u/escaping ([Crime])->()){
    guard let crimeURL = URL(string: "https://data.police.uk/api/crimes-street/drugs?poly=51.5623,-2.5709:51.4952,-2.7292:51.4008,-2.6235:51.4028,-2.4875:51.4569,-2.4274&date=2022-02") else {return}
    URLSession.shared.dataTask(with: crimeURL) { (data, _, _) in
        let crime = try! JSONDecoder().decode([Crime].self, from: data!)
        DispatchQueue.main.async{
            completion(crime)
        }
    }
    .resume()
}

To summarise, I'm trying to extract the longitude and latitude from each entry in the JSON file and append it to locationArray in this format Crime(latitude: 51.4586, longitude: -2.5936)

I'm relatively new to swift so any advice as to how to tackle this would be welcome!

EDIT:

As a basis, this was my attempt at the implementation of it, however it did not function :(

func getLocations() -> [Crime] {
    var locationArray : [Crime] = []
    getCrimes{ (crimes) in
        self.crimes = crimes
    }
    ForEach(crimes, id: \.self) crime in{
        locationArray.append(Crime(latitude:crimes.latitude, longitude: crimes.longitude))
    }
    return locationArray
}

EDIT 2:

From playing around a bit, I seem to have stumbled upon a "half-solution" below:

func getLocations() -> [Crime] {
    var locationArray : [Crime] = []
    getCrimes{ (crimes) in
        self.crimes = crimes
    }
    for crime in crimes{
        locationArray.append(Crime(latitude:crime.latitude, longitude: crime.longitude))
    }
    return locationArray
}

But I do receive this error when trying to run the simulator now: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "id", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))

So if anyone knows how to fix this please let me know!

CodePudding user response:

You should add a do-catch block wrapping the try!

func getAPI(completion: u/escaping ([Crime])->()){
    guard let crimeURL = URL(string: "https://data.police.uk/api/crimes-street/drugs?poly=51.5623,-2.5709:51.4952,-2.7292:51.4008,-2.6235:51.4028,-2.4875:51.4569,-2.4274&date=2022-02") else {return}
    URLSession.shared.dataTask(with: crimeURL) { (data, _, _) in
        do {
            let crime = try! JSONDecoder().decode([Crime].self, from: data!)
            DispatchQueue.main.async{
                completion(crime)
            }  
        } catch {
            print(error.localizedDescription)
        }
        
    }
    .resume()
}

The problem probably is because the Crime model doesn't match with the data, try changing the type of your id to Int:

struct Crime: Codable, Identifiable{
    var id: Int
    var latitude: Double
    var longitude: Double
}

CodePudding user response:

The issue is that you didn't provide the correct format for your Crime struct.

If you take a look at your JSON, it will look like this:

{
"category": "drugs",
"location_type": "Force",
"location": {
  "latitude": "51.461094",
  "street": {
    "id": 543495,
    "name": "On or near King Square Avenue"
  },
  "longitude": "-2.591658"
},
"context": "",
"outcome_status": {
  "category": "Further action is not in the public interest",
  "date": "2022-04"
},
"persistent_id": "7fb542fb5265c59ff0b613c473e69af6cf2b9421313c7cef28d626b1360523c5",
"id": 99489374,
"location_subtype": "",
"month": "2022-02"}

The latitude and longitude values are under the location node. Just adjust the code a little, and you are good to go:

struct Location: Codable {
    let latitude: String
    let longitude: String
}

struct Crime: Codable {
    let location: Location
    let id: Int

    enum CodingKeys: String, CodingKey {
        case location
        case id
 
    }
}

func getAPI(completion: @escaping ([Crime])->()){
    guard let crimeURL = URL(string: "https://data.police.uk/api/crimes-street/drugs?poly=51.5623,-2.5709:51.4952,-2.7292:51.4008,-2.6235:51.4028,-2.4875:51.4569,-2.4274&date=2022-02") else {return}
    URLSession.shared.dataTask(with: crimeURL) { (data, _, _) in
        do {
            let crime = try JSONDecoder().decode([Crime].self, from: data!)
            completion(crime)
        } catch {
            print(error)
        }
        
    }
    .resume()
}



//Call your method and you are good to go.
getAPI { crimes in
    print(crimes)
}
  • Related