Why isn’t the table view from a new View Controller not showing? Swift

563 views Asked by At

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 query goes here", sortBy: "sort by query 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!


Update:

After making the change from Andreas's and Paulw11's advice, the table view still did not load when the program was ran, and I got my last print statement being "Error" in the terminal from the catch block within the Task after creating an instance of the YelpApi class to make the initial API request(s) in NewViewController.swift. I've since changed this "Error" statement in the file to "Error occurred when making initial API endpoint request." for clarity.

Below is the updated versions of NewViewController.swift with the changes suggested by Andreas and Paulw11, and FetchData.swift with new print statements for helping to identify the new problem. Below these updated versions is the return statement from the terminal after making Andreas and Paulw11's suggested changes, which also contains the new print statements in the updated version of FetchData.swift (for helping to identify the new problem).

Updated version of NewViewController.swift with override inserted and async taken out:

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")
                self.tableView.reloadData()
            } catch {
                    //Handle error here.
                    //Prints.
                    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.
    
}

Updated version of FetchData.swift that includes new print statements to help identify the new problem:

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)"))
        
        //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.
            //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 Returned After Making Andreas and Paulw11's Suggested Changes and Running Program:

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 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
Print Check: Within repeat-while loop and before 'let businessResults = try JSONDecoder()' line of code.
Error occurred when making initial API endpoint request.

Had to quit program as it “stalled”/did nothing after making the last print statement above in the terminal.

Also, the “Date and Time, Project Name, and some other info [boringssl] boringssl_metrics_log_metric_block_invoke(153) Failed to log metrics” print statement comes up whenever I make an API request, and when researched, doesn’t cause any problems to the project.

The last print statement printed before "Error occurred when making initial API endpoint request." being "Print Check: Within repeat-while loop and before 'let businessResults = try JSONDecoder()' line of code." shows that something is going wrong at the line of code let businessResults = try JSONDecoder().decode(BusinessSearchResult.self, from:data), which leads me to think that the problem could be occurring and be related to when appending the queryItems for category and sort_by in the searchBusinesses function of the YelpApi class definition in FetchData.swift. I'm working on solving this, and will update if I figure this out.

Thanks for all the help!

1

There are 1 answers

8
Andreas On BEST ANSWER

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.