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!
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 thelocation
JSON response’s dictionary key, and in order to access it, I had to be usinglocation
instead oflocationOfRestaurant
. When I had usedlocation
for the property value for theVenue
struct earlier, and used theLocation
name for the struct that’s currently namedLocationOfRestaurant
, I got an error because I already had a struct elsewhere in the project already namedLocation
. This is why I had originally changed theVenue
struct’s property value name fromlocation” to
locationOfRestaurant(I thought the “location” JSON responses’s dictionary key’s
locationvalue could still be accessed this way) and the
Locationstruct in Venue.swift from
Locationto
LocationOfRestaurant```.The solution was changing the property named
locationOfRestaurant
in theVenue
struct tolocation
, and changing that property’s name wherever else in the project it was used, and leaving the name of the structLocationOfRestaurant
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 theVenue
struct below was originallylocation
in the code snippet I had originally posted in the question post, but was usinglocationOfRestaurant
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 forLocationOfRestaurant
struct below; originally usedLocation
in my initial question post, but was actually usingLocationOfRestaurant
at the time of the post, and when trying to solve the problem when collaborating with others here.After solution change:
Only code that was changed in
Venue.swift
:Thanks for the help everyone!