swiftxcodeopenweathermap

Why am I not able to recieve any data from the open weather api?


I am using swift and I want to receive data of the temp and humidity but all I am receiving is nil. I have two objects temp and humdity in another swift file. What am I doing wrong in my code? Not sure what I am missing.

struct Weather: Codable {
    var temp: Double?
    var humidity: Int?
    var name : String?
}

struct WeatherMain: Codable {
    let main: Weather
}


ViewController

class ViewController: UIViewController, CLLocationManagerDelegate {
    
    let locationManager = CLLocationManager()
    var latitudeValue = Double()
    var longitudeValue = Double()
    
    @IBOutlet weak var humidityLabel: UILabel!
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else { return }
        print("locations = \(locValue.latitude) \(locValue.longitude)")
        latitudeValue = locValue.latitude
        longitudeValue = locValue.longitude
    }
    
    func retrieve() {
        fetchWeather(lat: latitudeValue, lon: longitudeValue)
        { (response , error ) in
            for res in response! {
                print("Humid value is \(res.humidity ?? 0)")
            }
        }
    }
    
    @IBAction func showData(_ sender: Any) {
        retrieve()
    }
}
extension ViewController {
    
    func fetchWeather(lat: Double, //Required
                      lon: Double,
                      completionHandler: @escaping ([Weather]?, Error?) -> Void) {
        
        // MARK: Retrieve
        let apikey = "45345345345343454Fake API"
        /// create URL
        let baseURL = "https://api.openweathermap.org/data/2.5/weather?lat=\(lat)&lon=\(lon)&appid=\(apikey)"
        let url = URL(string: baseURL)
        print("this is the url for weather : \(url!)")
        /// Creating request
        var request = URLRequest(url: url!)
        request.setValue("Bearer \(apikey)", forHTTPHeaderField: "Authorization")
        request.httpMethod = "GET"
        URLSession.shared.dataTask(with: request) { (data, response, error) in
            if let err = error {
                print(err.localizedDescription)
            }
            do {
                /// Read data as JSON
                let json = try JSONSerialization.jsonObject(with: data!, options: [])
                /// Main dictionary
                guard let resp = json as? NSDictionary else { return }
                /// weather
                guard let weatherDic = resp.value(forKey: "weather") as? [NSDictionary] else { return }
                let weatherData = try? JSONDecoder().decode(WeatherMain.self, from: data!)
                var weatherList: [Weather] = []
                /// Accessing each weather
                for weatherObject in weatherDic {
                    if let weatherData = weatherData {
                        var weather = weatherData.main
                        //print("This is the temp \(weather.temp!)")
                        //print("This is the humidity \(weather.humidity!)")
                        weather.temp = weatherObject.value(forKey: "temp") as? Double
                        weather.humidity = weatherObject.value(forKey: "humidity") as? Int
                        weather.name = weatherObject.value(forKey: "name") as? String
                        weatherList.append(weather)
                    }
                }
                completionHandler(weatherList, nil)
            } catch {
                print("Caught error")
                completionHandler(nil, error)
            }
        }.resume()
    }
}

Solution

  • Well, as I see there are few mistakes in your code.

    1. You will never receive location updates due to that you haven't set CLLocationManagerDelegate, you haven't requested authorization to use location, you haven't asked location manager to start updating locations.
    2. Your response parsing code doesn't seems to be correct. You need to learn how to use Codable to parse JSON responses.

    I've modified a bit your code, so that it works. But prior copy-pasting please open your Info.plist and add the following keys and values for the keys. This is important step.

    Privacy - Location Usage Description

    Privacy - Location When In Use Usage Description

    The next step is to create correct Response model. To simplify the process of creating Response models you can use a website https://quicktype.io Here is what the website generated for that api response:

    // This file was generated from JSON Schema using quicktype, do not modify it directly.
    // To parse the JSON, add this file to your project and do:
    //
    //   let weatherResponse = try? newJSONDecoder().decode(WeatherResponse.self, from: jsonData)
    
    import Foundation
    
    // MARK: - WeatherResponse
    struct WeatherResponse: Codable {
        let coord: Coord?
        let weather: [Weather]?
        let base: String?
        let main: Main?
        let visibility: Int?
        let wind: Wind?
        let clouds: Clouds?
        let dt: Int?
        let sys: Sys?
        let timezone, id: Int?
        let name: String?
        let cod: Int?
    }
    
    // MARK: - Clouds
    struct Clouds: Codable {
        let all: Int?
    }
    
    // MARK: - Coord
    struct Coord: Codable {
        let lon, lat: Double?
    }
    
    // MARK: - Main
    struct Main: Codable {
        let temp, feelsLike, tempMin, tempMax: Double?
        let pressure, humidity: Int?
    
        enum CodingKeys: String, CodingKey {
            case temp
            case feelsLike = "feels_like"
            case tempMin = "temp_min"
            case tempMax = "temp_max"
            case pressure, humidity
        }
    }
    
    // MARK: - Sys
    struct Sys: Codable {
        let type, id: Int?
        let country: String?
        let sunrise, sunset: Int?
    }
    
    // MARK: - Weather
    struct Weather: Codable {
        let id: Int?
        let main, weatherDescription, icon: String?
    
        enum CodingKeys: String, CodingKey {
            case id, main
            case weatherDescription = "description"
            case icon
        }
    }
    
    // MARK: - Wind
    struct Wind: Codable {
        let speed, deg: Int?
    }
    

    And finally your updated ViewController

    import UIKit
    import CoreLocation
    
    class ViewController: UIViewController, CLLocationManagerDelegate {
    
    
        @IBOutlet weak var humidityLabel: UILabel!
        
        let locationManager = CLLocationManager()
        var location: CLLocation?
        
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            locationManager.delegate = self // set your CLLocationManagerDelegate to your ViewController instance
            
            checkAuthorizationStatus() // Check current authorization status
        }
    
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
     
            location = locations.first
        }
        
        func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
            
            checkAuthorizationStatus()
        }
        
        private func checkAuthorizationStatus() {
    
            var authorizationStatus: CLAuthorizationStatus!
            
            if #available(iOS 14.0, *) {
                authorizationStatus = locationManager.authorizationStatus
            } else {
                authorizationStatus = CLLocationManager.authorizationStatus()
            }
            
            switch authorizationStatus ?? .notDetermined {
            case .authorizedAlways, .authorizedWhenInUse: // If authorized
                locationManager.startUpdatingLocation() // request updating location
            case CLAuthorizationStatus.denied, CLAuthorizationStatus.restricted: // if denied we are not able to receive location updates
                print("Application doesn't have access to location.")
            case CLAuthorizationStatus.notDetermined: // if not determined we can request authorization
                locationManager.requestWhenInUseAuthorization() // request authorization
            @unknown default:
                print("Unknown authorization status")
            }
        }
    
        func retrieve() {
    
            guard let location = location else {
                print("Location is nil.")
                return
            }
            
            fetchWeather(forLocation: location) { [weak self] (result) in
    
                switch result {
                case .success(let weatherResponse):
                    
                    print("WeatherResponse: \(weatherResponse)")
                    
                    if let humidity = weatherResponse?.main?.humidity {
                        self?.humidityLabel.text = String(humidity)
                    }
                case .failure(let error):
                    
                    print("Error: \(error.localizedDescription)")
                }
            }
        }
        
        @IBAction func showData(_ sender: Any) {
    
            retrieve()
        }
    
    }
    
    
    
    extension ViewController {
    
        func fetchWeather(forLocation location: CLLocation, completion: @escaping (Result<WeatherResponse?, Error>) -> Void) {
        
            let apikey = "YOUR_API_KEY_HERE"
    
            let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&appid=\(apikey)")!
        
            var request = URLRequest(url: url)
            request.httpMethod = "GET"
        
            print("Request: \(url.absoluteString)")
            
            URLSession.shared.dataTask(with: request) { (data, response, error) in
            
                if let error = error {
                    
                    DispatchQueue.main.async { // Move completion to the main queue, so that you can work with UI stuff
                        completion(.failure(error))
                    }
                }
            
                guard let data = data else {
                    
                    print("No response data.")
                    
                    DispatchQueue.main.async { // Move completion to the main queue, so that you can work with UI stuff
                        completion(.success(nil))
                    }
                    
                    return
                }
                
                if let responseString = String(data: data, encoding: .utf8) { // Move completion to the main queue, so that you can work with UI stuff
                    print("Response: \(responseString)")
                }
            
                do {
                    let response = try JSONDecoder().decode(WeatherResponse.self, from: data) // Here is the magic of Codable, Just simply set expected Codable type
                    
                    DispatchQueue.main.async { // Move completion to the main queue, so that you can work with UI stuff
                        completion(.success(response))
                    }
                    
                } catch {
                    
                    DispatchQueue.main.async { // Move completion to the main queue, so that you can work with UI stuff
                        completion(.failure(error))
                    }
                }
            }.resume()
        }
    }
    

    Happy coding, don't give up)