This is a follow-up to the following question: How to call an instance of a class that makes an API call, and a function within that class that makes the request, and assign this to variable? Swift.
I’m trying to push a new view controller onto the stack, and present this view controller from an initial view controller, however when I run the program, the table view that is supposed to be shown in the new view controller doesn’t load on the simulator. I don't have any error notifications from Xcode at all before, during, or after running the program. Also, it doesn’t seem like code is being executed in this new view controller because print statements at the top/very beginning of the new view controller class (and view controller .swift file) aren’t being printed.
What is shown when the new table view from the new view controller is supposed to be shown is a blank screen, but with the navigation bar still at the top, with the back button in the top left of the navigation bar (like it usually is when the table view was shown correctly before changing to using the YelpApi class for the API request and using async/await). I'm also not getting any error messages in the terminal when this occurs.
What I think is related to the problem is the new YelpApi class that is being used to make the API endpoint request here, and using async/await. This problem didn’t occur until after I refactored my code using this new class and async/await.
What I think may be causing the problem more specifically, is I took out the “override” before “func viewDidLoad() async {“ in NewViewController.swift. I did this because I was getting an error when leaving it there, and found this solution which suggested to take it out, however, there is a problem with doing this as mentioned in the comments of the accepted answer (the problem being that there's no compile-time check that ensures you've got the signature right): Swift protocols: method does not override any method from its superclass.
I’ve already looked this problem (the table view not showing) up online including here, and couldn’t find a working solution. One similar post was this: View Controller not showing properly but my code is already set up similarly and in the same form as the accepted answer. I’ve also let the program run for 20 minutes before quitting the program in case the request was just taking a long time for whatever reason, however, the desired table view still was not presented.
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.
//Doesn't print.
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.
//Doesn't print.
func printCheckAfterIBOutletTableViewCode() {
print("Print Check: Right after tableView IBOutlet code at top of NewViewController.swift file.")
}
func viewDidLoad() async {
super.viewDidLoad()
//Function calls for print checks.
//Doesn't print.
self.printCheckBeforeIBOutletTableViewCode()
self.printCheckAfterIBOutletTableViewCode()
tableView.register(UINib(nibName: "CustomTableViewCell", bundle: nil), forCellReuseIdentifier: "CustomTableViewCell")
tableView.delegate = self
tableView.dataSource = self
//Print Check.
//Doesn't print.
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 quary goes here", sortBy: "sort by quary goes here")
self.tableView.reloadData()
} catch {
//Handle error here.
print("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) 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))
var results = [Venue]()
var expectedCount = 0
let countLimit = 50
var offset = 0
queryItems.append(URLQueryItem(name:"limit", value:"\(countLimit)"))
repeat {
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")
let (data, _) = try await URLSession.shared.data(for: request)
let businessResults = try JSONDecoder().decode(BusinessSearchResult.self, from:data)
expectedCount = min(businessResults.total,1000)
results.append(contentsOf: businessResults.businesses)
offset = businessResults.businesses.count
} while (results.count < expectedCount)
return results
}
}
Thanks!
CodePudding user response:
I think you identified one of the issues already. Your code in func viewDidLoad() async
will never be executed, because no-one calls it. By adding async
you are not overriding the original UIViewController
method anymore. (see https://developer.apple.com/documentation/uikit/uiviewcontroller/1621495-viewdidload)
Can you remove async
and override it again? I think that should at least execute the code and you should see your print statements.