1

I'm new to working with Swift and JSON, I'll try to describe what I'm trying to accomplish as best as I can.

I'm trying to access the Yelp API service and return and decode the JSON results and display the results in a list.

I've successfully been able to hit the API and log the results to the console, but I haven't been able to map the results to UI elements to display within a view.

Below is the struct for the results and the view I'm attempting to display the results in. I'm returning the error from my load function after the view loads.

Data.swift

import SwiftUI

struct BusinessesResponse: Codable {
    let restaurants: [RestaurantResponse]
}

struct RestaurantResponse: Codable, Identifiable {
    let id: String
    var name: String
    var coordinates: [longlat]
    var is_closed: Bool
    var category: String
    var imageURL: URL
    var url: URL
    var review_count: Int
   var rating: Double
   var display_phone: String
   var distance: Double
}

ContentView

import SwiftUI
import YelpAPI
import Combine
import CoreLocation

struct ContentView: View {

    @ObservedObject private var locationManager = LocationManager()
    @ObservedObject var fetcher = RestaurantFetcher()
    
    var body: some View {
        VStack {
            List(fetcher.businesses) { restaurant in
                VStack (alignment: .leading) {
                    Text(restaurant.name)
                }
              }
           }
        }
    }

    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
    }
}
public class RestaurantFetcher: ObservableObject {
    @Published var businesses = [RestaurantResponse]()
    
    init() {
        load(latitude: 28.4293403, longitude: -81.6241764)
    }
    
    func load(latitude: Double, longitude: Double) {
         let apikey = "API-KEY-HERE"
        let url = URL(string: "https://api.yelp.com/v3/businesses/search?latitude=\(latitude)&longitude=\(longitude)")!
        var request = URLRequest(url: url)
        request.setValue("Bearer \(apikey)", forHTTPHeaderField: "Authorization")
        request.httpMethod = "GET"
        
        URLSession.shared.dataTask(with: request) { (data, response, error) in
            do {
                if let d = data {
                    let decodedLists = try JSONDecoder().decode([RestaurantResponse].self, from: d)
                    DispatchQueue.main.async {
                        self.businesses = decodedLists
                    }
                } else {
                    print("No Data")
                }
            } catch {
                print ("Caught")
            }
        }.resume()
    }
}

JSON response from Yelp's API

{
    "businesses": [
        {
            "id": "ZTgp2l3XbADwmOMM5rpWZg",
            "alias": "disneys-oak-trail-golf-course-lake-buena-vista",
            "name": "Disney's Oak Trail Golf Course",
            "image_url": "https://s3-media1.fl.yelpcdn.com/bphoto/G3oE_KJJ53H1iweD-j83yQ/o.jpg",
            "is_closed": false,
            "url": "https://www.yelp.com/biz/disneys-oak-trail-golf-course-lake-buena-vista?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
            "review_count": 12,
            "categories": [
                {
                    "alias": "golf",
                    "title": "Golf"
                }
            ],
            "rating": 3.5,
            "coordinates": {
                "latitude": 28.4055855,
                "longitude": -81.5956011
            },
            "transactions": [],
            "location": {
                "address1": "1950 W Magnolia Palm Dr",
                "address2": "",
                "address3": "",
                "city": "Lake Buena Vista",
                "zip_code": "32836",
                "country": "US",
                "state": "FL",
                "display_address": [
                    "1950 W Magnolia Palm Dr",
                    "Lake Buena Vista, FL 32836"
                ]
            },
            "phone": "+14079394653",
            "display_phone": "(407) 939-4653",
            "distance": 3845.3340908128034
        },
        {
            "id": "VVF9h1jhhOVXIvxe-MDK8g",
            "alias": "panther-lake-golf-course-winter-garden",
            "name": "Panther Lake Golf Course",
            "image_url": "https://s3-media1.fl.yelpcdn.com/bphoto/ff47f9jXs56s3Cf7obIapA/o.jpg",
            "is_closed": false,
            "url": "https://www.yelp.com/biz/panther-lake-golf-course-winter-garden?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
            "review_count": 1,
            "categories": [
                {
                    "alias": "hotels",
                    "title": "Hotels"
                },
                {
                    "alias": "golf",
                    "title": "Golf"
                }
            ],
            "rating": 4.0,
            "coordinates": {
                "latitude": 28.4419223,
                "longitude": -81.6303836
            },
            "transactions": [],
            "location": {
                "address1": "16301 Phil Ritson Way",
                "address2": "",
                "address3": "",
                "city": "Winter Garden",
                "zip_code": "34787",
                "country": "US",
                "state": "FL",
                "display_address": [
                    "16301 Phil Ritson Way",
                    "Winter Garden, FL 34787"
                ]
            },
            "phone": "+14076562626",
            "display_phone": "(407) 656-2626",
            "distance": 1620.1533458028462
        },
        {
            "id": "UdqKnhBDg4b04e38qFcjEA",
            "alias": "orange-83-pub-and-grill-winter-garden",
            "name": "Orange 83 Pub And Grill",
            "image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/KjWvn26iBv13GnIUCW7z9Q/o.jpg",
            "is_closed": false,
            "url": "https://www.yelp.com/biz/orange-83-pub-and-grill-winter-garden?adjust_creative=s-hyKAjsx6P4UW-uqMn7aQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=s-hyKAjsx6P4UW-uqMn7aQ",
            "review_count": 1,
            "categories": [
                {
                    "alias": "pubs",
                    "title": "Pubs"
                }
            ],
            "rating": 4.0,
            "coordinates": {
                "latitude": 28.4419223,
                "longitude": -81.6303836
            },
            "transactions": [],
            "location": {
                "address1": "16301 Phil Ritson Way",
                "address2": null,
                "address3": "Orange County National Golf Center & Lodge",
                "city": "Winter Garden",
                "zip_code": "34787",
                "country": "US",
                "state": "FL",
                "display_address": [
                    "16301 Phil Ritson Way",
                    "Orange County National Golf Center & Lodge",
                    "Winter Garden, FL 34787"
                ]
            },
            "phone": "+14076562626",
            "display_phone": "(407) 656-2626",
            "distance": 1620.1533458028462
        }
    ],
    "total": 3,
    "region": {
        "center": {
            "longitude": -81.6241764,
            "latitude": 28.4293403
        }
    }
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209
Jason Tremain
  • 1,259
  • 3
  • 20
  • 35

1 Answers1

1

First of all, note that at the top level of your JSON response there is an object { } and not an array [ ].

Which means you need to decode BusinessesResponse instead of [RestaurantResponse]:

let response = try JSONDecoder().decode(BusinessesResponse.self, from: d)
self.businesses = response.restaurants

Also note you're trying to decode restaurants and in the JSON response you have businesses. You can either rename your field inside BusinessesResponse to restaurants or, which might be better, use CodingKeys:

struct BusinessesResponse: Codable {
    enum CodingKeys: String, CodingKey {
        case restaurants = "businesses"
    }

    let restaurants: [RestaurantResponse]
}

Also note you have category and imageUrl fields which don't exist in the JSON response. And the categories field in the JSON response is an array of objects.

Instead you can do:

struct RestaurantResponse: Codable, Identifiable {
    enum CodingKeys: String, CodingKey {
        case id, name, is_closed, categories, url, review_count, rating, display_phone, distance
        case imageURL = "image_url"
    }

    ...
    var categories: [RestaurantCategory]
    var imageURL: URL
    ...
}

struct RestaurantCategory: Codable {
    var alias: String
    var title: String
}

If you decide to use CodingKeys then you can also change your other variables: is_closed, review_count to camelCase.

Alternatively if all your variables in the model will be the camelCase equivalents of the snake_case keys in the JSON response you can use:

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • Using the above struct for RestaurantCategory to access the nested array of information in the JSON response, how would I go about displaying that in a text view? Text(restaurant.title) isn't working, but obviously I'm wrong in using that. – Jason Tremain Jul 01 '20 at 22:06
  • 1
    `var categories: [RestaurantCategory]` is a collection of elements which means one restaurant can have multiple categories. Which do you want to display? If the first one, you can do `restaurant.categories[0].title` (assuming `categories` is not empty). – pawello2222 Jul 01 '20 at 22:11
  • Thanks that answered my question. Ideally, I plan to display all the categories, but I can put an if statement on it to control if it displays. Thank you for the help, I appreciate it. – Jason Tremain Jul 01 '20 at 22:14
  • 1
    You can display all categories in one `Text` view or have multiple views in a `ForEach`. But I recommend you create a new question where you can specify exactly how you want to display your categories. I'll be happy to help you there. – pawello2222 Jul 01 '20 at 22:23