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()
}
}
Well, as I see there are few mistakes in your code.
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)