anonymousSwift
anonymousSwift

Reputation: 99

How to fix TableView numberOfRowsInSection returning nil?

I'm trying to retrieve a 5 day forecast using the OpenWeatherMap API, I'm not sure why but each time I call my weatherCount() method it returns nil.

In the view model I use a print statement to verify the number of rows should be 40. I have tried to use guard statements and force unwrapping which just crashes the program. I tried implementing callback methods but don't think I did them correctly.

WeatherViewModel

import Foundation
class WeatherViewModel {

    var weatherInfo: WeatherData?

    weak var delegate: WeatherDelegate?

    func getWeatherData() {
        let weather = "https://api.openweathermap.org/data/2.5/forecast?q=London,GB&appid=fe3e0ecae7e573d25b37542f96f66f1a"
        guard let url = URL(string: weather) else {
            print("Could not reach the API endpoint") // this guard is not being hit
            return }

        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in //data task object, completion handler(trailing closure)

            DispatchQueue.main.async {
                guard error == nil else { // Checking for errors in the request
                    print("Error retrieved was: \(error)")
                    return
                }

                guard let weatherResponse = data else { //checks we got the data from request
                    print("Could not retrieve data instead got \(data)")
                    return }
            }

            do {
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .secondsSince1970

                let responseData = try decoder.decode(WeatherData.self, from: data!)
                DispatchQueue.main.async {
                    // print("Delegate method shows \(self.delegate?.didRecieve(forecast: responseData))")
                    self.weatherInfo = responseData
                    print(self.weatherInfo)
                    print("Number of rows in section will be : \(self.weatherInfo?.list.count ?? 1)")
                }
            }
            catch let e as Error {
                print("Error creating current weather from JSON because: \(e.localizedDescription)")
                print("Error in parsing the JSON")
                NSLog("Error hit when calling weather service \(e)")
            }
        }
        task.resume()
    }
    func weatherCount() -> Int {
        let numberOfRows = self.weatherInfo?.list.count
        print("Number of rows in weatherCount : \(numberOfRows)")
        return numberOfRows ?? 1
    }
}

WeatherTableViewController

import UIKit
import Foundation

class WeatherTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet var tableView: UITableView!

    lazy var viewModel: WeatherViewModel = {
        return WeatherViewModel()
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.delegate = self
        self.tableView.dataSource = self

        DispatchQueue.main.async {
                self.viewModel.getWeatherData()
                self.tableView.reloadData()
            }
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 5
    }

   func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        //print("Number of rows in section is: \(viewModel.weatherInfo?.list.count)")
        //print("Rows: \(viewModel.weatherCount())")
        return viewModel.weatherCount() ?? 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let weatherCell = tableView.dequeueReusableCell(withIdentifier: "WeatherCell", for: indexPath)
        weatherCell.textLabel?.text = " The current temperature is : \(viewModel.weatherInfo?.list[indexPath.row].main?.temp ?? 0)"
        print(viewModel.weatherInfo?.list[indexPath.row].main?.temp)
        return weatherCell
    }
}

numberOfRowsInSection should return 40 however returns nil

Weather

import Foundation

struct WeatherData: Decodable {
    let cod: String
    let message: Double
    let cnt: Int
    let list: [Info]
}

struct Info: Decodable {
    let dt: Date
    let main: WeatherInfo?

}

struct WeatherInfo: Decodable {
    let temp: Double
    let temp_min: Double
    let temp_max: Double
    let pressure: Double
    let sea_level: Double
    let grnd_level: Double
    let humidity: Int
    let temp_kf: Double
}

private enum CodingKeys: String, CodingKey {

    case minTemp = "temp_min"
    case maxTemp = "temp_max"
    case seaLevel = "sea_level"
    case temp
    case pressure
    case groundLevel = "grnd_level"
    case humidity
    case temp_kf
}

Upvotes: 3

Views: 755

Answers (1)

Kamran
Kamran

Reputation: 15238

Use a completion handler to get notify from the weather data parsing and then reload tableView as below,

func getWeatherData(_ completion: @escaping () -> Void) {
       let weather = "https://api.openweathermap.org/data/2.5/forecast?q=London,GB&appid=fe3e0ecae7e573d25b37542f96f66f1a"
        guard let url = URL(string: weather) else {
            print("Could not reach the API endpoint") // this guard is not being hit
            return }

        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in //data task object, completion handler(trailing closure)

                guard error == nil else { // Checking for errors in the request
                    print("Error retrieved was: \(error)")
                    return
                }

                guard let weatherResponse = data else { //checks we got the data from request
                    print("Could not retrieve data instead got \(data)")
                    return 
                }

            do {
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .secondsSince1970

                let responseData = try decoder.decode(WeatherData.self, from: data!)
                DispatchQueue.main.async {
                    // print("Delegate method shows \(self.delegate?.didRecieve(forecast: responseData))")
                    self.weatherInfo = responseData
                    completion()
                }
            }
            catch let e as Error {
                print("Error creating current weather from JSON because: \(e.localizedDescription)")
                print("Error in parsing the JSON")
                NSLog("Error hit when calling weather service \(e)")
            }
        }
        task.resume()
}

Update viewDidLoad as,

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.delegate = self
    self.tableView.dataSource = self

    self.viewModel.getWeatherData() {
        self.tableView.reloadData()
    }
}

Upvotes: 3

Related Questions