0

Tldr:

*Update: Updated code and post body.

I’m trying to show a table view in a new view controller, but I keep getting an error thrown from the async method searchBusinesses in the YelpApi class in FetchData.swift.

I’ve printed the JSON response after let (data, _) = try await URLSession.shared.data(for: request) in FetchData.swift, however, the response looks fine/there doesn’t seem to be any errors or indications of a problem in it (a snippet of this response is at the bottom of the Returned Print Statement "file" at the bottom of this post).

I've also printed the error description message at the bottom of this post in the Returned Print Statement "file".

Main thing I think the problem is related to:

-Code wrong somehow in code for decoding data from API request in line of code: let businessResults = try JSONDecoder().decode(BusinessSearchResult.self, from: data) in searchBusinesses method in FetchData.swift.


Rest of post, including all code “files”:

This is a follow-up to the following question: Why isn’t the table view from a new View Controller not showing? Swift.

I’m trying to show a table view in a new view controller, but I keep getting an error thrown from the async method searchBusinesses in the YelpApi class in FetchData.swift.

I also looked this problem up online, and re-read the documentation for the following topics (with the exception of JSONDecoder(); first time reading it), and still could not identify the problem:

-Error Handling https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html

-URLSession https://developer.apple.com/documentation/foundation/urlsession

-JSONDecoder() https://developer.apple.com/documentation/foundation/jsondecoder

I think the problem could be:

-The function for generating the openAt time parameter returns a value that somehow causes an error during the API request, and/or when decoding the data. However the print statements within this function for generating the openAt time indicates that the code within this function is working as it should.

-Code wrong somehow when making the API request in line of code: let (data, _) = try await URLSession.shared.data(for: request) in searchBusinesses method (in the YelpApi class in FetchData.swift).

-Code wrong somehow in code for decoding data from API request in line of code: let businessResults = try JSONDecoder().decode(BusinessSearchResult.self, from: data) in searchBusinesses method.

*Note about code changes: I had recently changed the location property in my Venues struct which conforms to Codable to locationOfRestaurant and had forgot to update the code "file" here to reflect that. After receiving errors after this change, I've since changed it back to location. *The error description message at the bottom of this post referred to this locationOfRestaurant property.

Code:

InitialViewController.swift:

//*Code for creating a table view that shows options to the user, for the user to select.*

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        //*Code for assigning values to variables related to what row in the table view the user selected.*
        
        
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let newVC = storyboard.instantiateViewController(identifier: "NewViewController") as! NewViewController
        newVC.modalPresentationStyle = .fullScreen
        newVC.modalTransitionStyle = .crossDissolve
        
        //Print Check.
        //Prints.
        print("Print Check: Right before code for presenting the new view controller.")

        navigationController?.pushViewController(newVC, animated: true)
        
    }

NewViewController.swift:

import UIKit
import CoreLocation

class NewViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    //Print Check.
    //Prints.
    func printCheckBeforeIBOutletTableViewCode() {
        print("Print Check: Right before tableView IBOutlet code at top of NewViewController.swift file.")
    }
    
    @IBOutlet var tableView: UITableView!
    
    var venues: [Venue] = []
    
    //Print Check.
    //Prints.
    func printCheckAfterIBOutletTableViewCode() {
        print("Print Check: Right after tableView IBOutlet code at top of NewViewController.swift file.")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //Function calls for print checks.
        //Prints.
        self.printCheckBeforeIBOutletTableViewCode()
        self.printCheckAfterIBOutletTableViewCode()
        
        tableView.register(UINib(nibName: "CustomTableViewCell", bundle: nil), forCellReuseIdentifier: "CustomTableViewCell")
        tableView.delegate = self
        tableView.dataSource = self
        
        //Print Check.
        //Prints.
        print("Print Check: Right before creating an instance of YelpApi class, then creating a task to make the API request.")
        
        let yelpApi = YelpApi(apiKey: "Api key")
        
        Task {
            do {
                self.venues = try await yelpApi.searchBusiness(latitude: selectedLatitude, longitude: selectedLongitude, category: "category query goes here", sortBy: "sort by query goes here", openAt: functionForGeneratingOpenAtParameter())
                self.tableView.reloadData()
            } catch {
                    //Handle error here.
                    //Prints.
                    print("Error info for catch block in Task block in NewViewController.swift for making API request: \(error)")
            }
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
           return venues.count
       }
       
       func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
           let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
           
           //Details for custom table view cell go here.
       }
           
       //Rest of table view protocol functions.
    
}

Venue.swift:

import Foundation

// MARK: - BusinessSearchResult
struct BusinessSearchResult: Codable {
    let total: Int
    let businesses: [Venue]
    let region: Region
}

// MARK: - Business
struct Venue: Codable {
    let rating: Double
    let price, phone, alias: String?
    let id: String
    let isClosed: Bool?
    let categories: [Category]
    let reviewCount: Int?
    let name: String
    let url: String?
    let coordinates: Center
    let imageURL: String?
    let location: Location
    let distance: Double
    let transactions: [String]

    enum CodingKeys: String, CodingKey {
        case rating, price, phone, id, alias
        case isClosed
        case categories
        case reviewCount
        case name, url, coordinates
        case imageURL
        case location, distance, transactions
    }
}

// MARK: - Category
struct Category: Codable {
    let alias, title: String
}

// MARK: - Center
struct Center: Codable {
    let latitude, longitude: Double
}

// MARK: - Location
struct Location: Codable {
    let city, country, address2, address3: String?
    let state, address1, zipCode: String?

    enum CodingKeys: String, CodingKey {
        case city, country, address2, address3, state, address1
        case zipCode
    }
}

// MARK: - Region
struct Region: Codable {
    let center: Center
}

FetchData.swift:

import Foundation
import CoreLocation

class YelpApi {
    
    private var apiKey: String
    
    init(apiKey: String) {
        self.apiKey = apiKey
    }
    
    func searchBusiness(latitude: Double,
                        longitude: Double,
                        category: String,
                        sortBy: String,
                        openAt: Int?) async throws -> [Venue] {
        
        var queryItems = [URLQueryItem]()
        queryItems.append(URLQueryItem(name:"latitude",value:"\(latitude)"))
        queryItems.append(URLQueryItem(name:"longitude",value:"\(longitude)"))
        queryItems.append(URLQueryItem(name:"categories", value:category))
        queryItems.append(URLQueryItem(name:"sort_by",value:sortBy))
        
        if let openAt = openAt {
            queryItems.append(URLQueryItem(name:"open_at", value:"\(openAt)"))
        }
       
        var results = [Venue]()
        
        var expectedCount = 0
        let countLimit = 50
        var offset = 0
        
        queryItems.append(URLQueryItem(name:"limit", value:"\(countLimit)"))
        
        //Print Check.
        //Prints.
        print("Print Check: Line before repeat-while loop.")
        
        repeat {
            
            //Print Check.
            //Prints.
            print("Print Check: Within repeat-while loop and before first line of code within it.")
            
            var offsetQueryItems = queryItems
            
            offsetQueryItems.append(URLQueryItem(name:"offset",value: "\(offset)"))
            
            var urlComponents = URLComponents(string: "https://api.yelp.com/v3/businesses/search")
            urlComponents?.queryItems = offsetQueryItems
            
            guard let url = urlComponents?.url else {
                throw URLError(.badURL)
            }
            
            var request = URLRequest(url: url)
            request.setValue("Bearer \(self.apiKey)", forHTTPHeaderField: "Authorization")
            
            //Print Check.
            //Prints.
            print("Print Check: Within repeat-while loop and before 'let (data, _) = try await' line of code.")
            let (data, _) = try await URLSession.shared.data(for: request)
            
            //Print Check for printing the JSON response.
            //Prints.
            print(String(decoding: data, as: UTF8.self))
            
            //Print Check.
            //Prints.
            print("Print Check: Within repeat-while loop and before 'let businessResults = try JSONDecoder()' line of code.")
            let businessResults = try JSONDecoder().decode(BusinessSearchResult.self, from:data)
            
            //Print Check.
            //Doesn't print.
            print("Print Check: Within repeat-while loop and right after 'let businessResults = try JSONDecoder()' line of code.")

            expectedCount = min(businessResults.total,1000)
            
            results.append(contentsOf: businessResults.businesses)
            offset += businessResults.businesses.count
        } while (results.count < expectedCount)
        
        //Print Check.
        //Doesn't print.
        print("Print Check: After repeat-while loop and before 'return results' code.")
        
        return results
    }
}

Returned Print Statements From Terminal, including the JSON response and catch block error description at the end:

Print Check: Right before code for presenting the new view controller.
Print Check: Right before tableView IBOutlet code at top of NewViewController.swift file.
Print Check: Right after tableView IBOutlet code at top of NewViewController.swift file.
Print Check: Right before creating an instance of YelpApi class, then creating a task to make the API request.
Print statements from functionForGeneratingOpenAtParameter(). These print statements indicate the function is working as it should, and don't seem to indicate a problem.
Print Check: Line before repeat-while loop.
Print Check: Within repeat-while loop and before first line of code within it.
Print Check: Within repeat-while loop and before 'let (data, _) = try await' line of code.
Date and Time, Project Name, and some other info [boringssl] boringssl_metrics_log_metric_block_invoke(153) Failed to log metrics
*Note only showed JSON response for a few businesses because of post character limit: {"businesses": [{"id": "hZR-LKgsooHaN6a8L2dprg", "alias": "tako-cheena-orlando", "name": "Tako Cheena", "image_url": "https://s3-media4.fl.yelpcdn.com/bphoto/qj1uAI1X5wJZeSbfCQs17w/o.jpg", "is_closed": false, "url": "https://www.yelp.com/biz/tako-cheena-orlando?adjust_creative=mgN_4fA5wlIrHQMgamUFAQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=mgN_4fA5wlIrHQMgamUFAQ", "review_count": 1709, "categories": [{"alias": "asianfusion", "title": "Asian Fusion"}, {"alias": "mexican", "title": "Mexican"}, {"alias": "empanadas", "title": "Empanadas"}], "rating": 4.0, "coordinates": {"latitude": 28.558364, "longitude": -81.3646545}, "transactions": ["pickup", "delivery"], "price": "$", "location": {"address1": "948 N Mills Ave", "address2": "", "address3": "", "city": "Orlando", "zip_code": "32803", "country": "US", "state": "FL", "display_address": ["948 N Mills Ave", "Orlando, FL 32803"]}, "phone": "+14077570626", "display_phone": "(407) 757-0626", "distance": 2650.3293007456186}, {"id": "KlZAG6XPK0GFLAZHkuUPNA", "alias": "wawa-winter-park", "name": "Wawa", "image_url": "https://s3-media1.fl.yelpcdn.com/bphoto/r5-YgTXO0ez_syOSNq9Uhg/o.jpg", "is_closed": false, "url": "https://www.yelp.com/biz/wawa-winter-park?adjust_creative=mgN_4fA5wlIrHQMgamUFAQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=mgN_4fA5wlIrHQMgamUFAQ", "review_count": 44, "categories": [{"alias": "servicestations", "title": "Gas Stations"}, {"alias": "coffee", "title": "Coffee & Tea"}, {"alias": "sandwiches", "title": "Sandwiches"}], "rating": 4.5, "coordinates": {"latitude": 28.604943, "longitude": -81.365896}, "transactions": ["pickup", "delivery"], "price": "$", "location": {"address1": "901 N Orlando Ave", "address2": "", "address3": "", "city": "Winter Park", "zip_code": "32789", "country": "US", "state": "FL", "display_address": ["901 N Orlando Ave", "Winter Park, FL 32789"]}, "phone": "+14076290167", "display_phone": "(407) 629-0167", "distance": 7550.802900576834}, 
Print Check: Within repeat-while loop and before 'let businessResults = try JSONDecoder()' line of code.
Error info for catch block in Task block in NewViewController.swift for making API request: keyNotFound(CodingKeys(stringValue: "locationOfRestaurant", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "businesses", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"locationOfRestaurant\", intValue: nil) (\"locationOfRestaurant\").", underlyingError: nil))

The relevant error message:

Error info for catch block in Task block in NewViewController.swift for making API request: keyNotFound(CodingKeys(stringValue: "locationOfRestaurant", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "businesses", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: "locationOfRestaurant", intValue: nil) ("locationOfRestaurant").", underlyingError: nil))

Thanks!

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
coder
  • 19
  • 4
  • 2
    There is a `try`, but no `catch { print("Error: \(error)")}`? – Larme Jun 07 '22 at 09:52
  • Wow, thank you very much. I will try this and update. Appreciate it! – coder Jun 07 '22 at 09:57
  • 2
    Also, is it a GET request? Otherwise you should set request.httpMethod = "POST", when you add the catch you'll be able to better understand the error as well. – André Henrique da Silva Jun 07 '22 at 10:01
  • I read up on this, and tried this, however I'm still not able to solve the problem (included a ```do``` block as well). Can you please post a quick example for the positioning of that catch block you have there? Thanks for the help! – coder Jun 07 '22 at 10:28
  • @AndréHenriquedaSilva Yes, it is a GET request. Thank you for the info though! – coder Jun 07 '22 at 10:37
  • 1
    `print("Error")`that prints right? Then print the actual error! -> `print("Error: \(error)")` and tell us what's the output of that – Larme Jun 07 '22 at 13:32
  • @Larme Ah got you, will do! Thanks! – coder Jun 07 '22 at 18:02
  • 1
    FWIW, I tried decoding using your `BusinessSearchResult` [with a random query](https://gist.github.com/robertmryan/6e59f99c7fe2551581433cb1786dc245) and that worked fine. So your problem likely rests elsewhere. (That having been said, your two `CodingKeys` are redundant and can be removed.) – Rob Jun 07 '22 at 18:34
  • @Larme Just updated the post with the error description output. It seems to be a problem with the ```CodingKeys``` for ```locationOfRestaurant```. Also, as @Rob just mentioned; they're redundant. Working on this now with his suggestions and info from the error description. Thanks for the help! – coder Jun 07 '22 at 21:11
  • @Rob Thank you for the link and the info! Working on this now, and will update. I've also update my post with the error description output for the catch block. Thanks! – coder Jun 07 '22 at 21:16
  • 1
    There seems to be a `locationOfRestaurant` property in your Codable, but it's not shown. In the first `businesses` value which is an array, your structure expects one, and it's not there. Either it's an issue on the whole decoding (it's not at that level, never is), or it might not be present, but you didn't make iit optional, you always expect it to be there... – Larme Jun 07 '22 at 21:23
  • @Rob I tried removing both of the ```CodingKeys``` you mentioned in ```Venue.Swift```, however, I'm still getting the same return print statement and error description output that's in the updated post. Working on solving this. – coder Jun 07 '22 at 21:34
  • 1
    What is this codingkey of `locationOfRestaurant`? I don't see that in any of the code in your question. – Rob Jun 07 '22 at 22:06
  • 2
    BTW, I would suggest deleting all this kruft in your question. It's not helping. Reduce the problem down to the bare minimum (the “M” of [MCVE](https://stackoverflow.com/help/mcve)) necessary to reproduce the problem. You really are making it much harder for us to help you. I had to hunt and hunt to find your error message! – Rob Jun 07 '22 at 22:08
  • @Rob Recently, I had changed the ```location``` property in my Codable to ```locationOfRestaurant``` to be more descriptive, and had forgot to update the code "file" here to reflect that. However, this gave me problems as I've come to realize. I've changed it back to ```location``` and have commented about this process in the post body. – coder Jun 07 '22 at 23:23
  • @Larme Yes, I had changed the ```location``` property in my Codable to ```locationOfRestaurant``` and had forgot to update the code "file" here to reflect that. However, this gave me problems as I've come to realize. I've changed it back to ```location``` and have commented about this process in the post body. This was the only thing that wasn't fully updated in all of the code "files". Also, thank you, your comment helped me solve a big chunk of this problem! This led me to other smaller problems, but I think I'm almost done solving them. Thanks! – coder Jun 07 '22 at 23:25
  • @Rob Thank you, agreed, I was considering also having just the error message near the top of the post earlier. Also. thank you for the link and editing the post! I was working on solving the rest of this problem earlier, thanks! – coder Jun 07 '22 at 23:33
  • Cool. So hopefully that answered your question (or at least the question you raised here). I’d suggest that you just go ahead and delete this question at this point. While one theoretically can [answer one’s own question](https://stackoverflow.com/help/self-answer), with no offense, this question is unlikely to be terribly useful to future readers, so it should probably just be deleted (or closed). – Rob Jun 08 '22 at 18:05
  • @Rob, Just saw this. I've been working on my own answer that I came to, thanks to y'all's post and plan to post it here. Thanks for the info, but I think it could help someone in the future if they have a similar problem. Thanks! – coder Jun 08 '22 at 23:03
  • Hi everybody, update: I figured out the problem thanks to y’all’s help, and was able to get the table view to show without any errors occurring. The solution was changing the ```Venue``` struct property’s name that was most recently ```locationInRestaurant``` to ```location```, as this is what the JSON’s response’s key’s name is for this data (it’s ```location```, as is shown in the ```Returned Print Statements``` file at the bottom of this post, and in the Yelp Fusion API “Search” Endpoint documentation), continued... – coder Jun 08 '22 at 23:12
  • ... and changing that property’s name wherever else in the project it was used as mentioned, and leaving the ```LocationOfRestaurant``` struct’s name as is. After making this one change, I had errors for the same basic problem in other parts of my project’s code, and when those were fixed, the table view was shown, and no other errors came up. Thanks for the help everyone! I’ll be posting this as the answer with a code example. – coder Jun 08 '22 at 23:13

1 Answers1

0

Found out the solution to the problem (thanks to everyone's help!)

In case anyone wanted to see the final answer:

The problem was that the property named locationOfRestaurant in the Venue struct was for the location JSON response’s dictionary key, and in order to access it, I had to be using location instead of locationOfRestaurant. When I had used location for the property value for the Venue struct earlier, and used the Location name for the struct that’s currently named LocationOfRestaurant, I got an error because I already had a struct elsewhere in the project already named Location. This is why I had originally changed the Venue struct’s property value name from location” to locationOfRestaurant(I thought the “location” JSON responses’s dictionary key’slocationvalue could still be accessed this way) and theLocationstruct in Venue.swift fromLocationtoLocationOfRestaurant```.

The solution was changing the property named locationOfRestaurant in the Venue struct to location, and changing that property’s name wherever else in the project it was used, and leaving the name of the struct LocationOfRestaurant as is.

After making this one change, I had errors for the same basic problem in other parts of my project’s code, and when those were fixed, the table view was shown, and no other errors came up.

Code before and after change:

*Note: Also took out the two coding keys in this solution, because they were redundant, as noted by @Rob.

Before solution change:

Only code that was eventually changed in Venue.swift:

*Note: locationOfRestaurant property in the Venue struct below was originally location in the code snippet I had originally posted in the question post, but was using locationOfRestaurant in the code that I was running and using when I made the initial post, and throughout trying to solve the problem when collaborating with others here. Same thing for LocationOfRestaurant struct below; originally used Location in my initial question post, but was actually using LocationOfRestaurant at the time of the post, and when trying to solve the problem when collaborating with others here.

// MARK: - Business
struct Venue: Codable {
    let locationOfRestaurant: LocationOfRestaurant
}

// MARK: - Location
struct LocationOfRestaurant: Codable {
    let city, country, address2, address3: String?
    let state, address1, zipCode: String?
}

After solution change:

Only code that was changed in Venue.swift:

// MARK: - Business
struct Venue: Codable {
    let location: LocationOfRestaurant
}

// MARK: - LocationOfRestaurant
struct LocationOfRestaurant: Codable {
    let city, country, address2, address3: String?
    let state, address1, zipCode: String?
}

Thanks for the help everyone!

coder
  • 19
  • 4