I´m using OpenWeatherMap api (https://openweathermap.org/forecast5#5days) to make a WeatherApp using swift, i already got the current weather data, but now i want to get the 5 days forecast, how could i do it if i only want to get the list atribute and the list atribute nested to ResponseApi. Please help.
I already tried to make it work, but i failed, makeDataRequestForecast its the function that retrieves the data from the api.
WeatherService
import CoreLocation
import Foundation
public final class WeatherService: NSObject {
public let locationManager = CLLocationManager()
private let API_KEY = "<api-key>"
public var completionHandler: ((Weather) -> Void)?
public var completionHandler2: ((Forecast) -> Void)?
public override init(){
super.init()
locationManager.delegate = self
}
public func loadWeatherData(_ completionHandler: @escaping((Weather)->Void)) {
self.completionHandler = completionHandler
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
public func loadForecastData(_ completionHandler: @escaping((Forecast)->Void)) {
self.completionHandler2 = completionHandler
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
//api.openweathermap.org/data/2.5/forecast?lat=44.34&lon=10.99&appid={API key}
private func makeDataRequestForecast(forCoordinates coordinates: CLLocationCoordinate2D){
guard let urlString = "https://api.openweathermap.org/data/2.5/forecast?lat=\(coordinates.latitude)&lon=\(coordinates.longitude)&appid=\(API_KEY)&units=metric".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return}
guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url){ data, response, error in
guard error == nil,let data = data else {return}
if let response = try? JSONDecoder().decode(ForecastApi.self, from: data) {
let weatherList = response.list.map { Weather(response: $0) }
let forecast = Forecast(list: weatherList)
print(response)
self.completionHandler2?(forecast)
print(response)
}else{
print("error")
}
}.resume()
}
private func makeDataRequest(forCoordinates coordinates: CLLocationCoordinate2D){
guard let urlString = "https://api.openweathermap.org/data/2.5/weather?lat=\(coordinates.latitude)&lon=\(coordinates.longitude)&appid=\(API_KEY)&units=metric".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return}
guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url){ data, response, error in
guard error == nil,let data = data else {return}
if let response = try? JSONDecoder().decode(ResponseApi.self, from: data){
let weather = Weather(response: response)
self.completionHandler?(weather)
}else{
print("error")
}
}.resume()
}
}
extension WeatherService: CLLocationManagerDelegate{
public func locationManager(
_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {return}
print("Location \(location.coordinate)")
makeDataRequest(forCoordinates: location.coordinate)
makeDataRequestForecast(forCoordinates: location.coordinate)
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error: \(error.localizedDescription)")
}
}
struct ForecastApi:Decodable{
let list:[ResponseApi]
}
struct ResponseApi:Decodable{
let name:String
let timezone:Int
let wind:Wind
let clouds:Clouds
let main: MainApi
let weather:[WeatherApi]
}
struct Wind:Decodable{
let speed:Double
}
struct Clouds:Decodable{
let all:Double
}
struct MainApi:Decodable{
let temp: Double
let humidity:Int
}
struct WeatherApi : Decodable{
let description:String
let icon:String
enum CodingKeys: String,CodingKey{
case description
case icon = "main"
}
}
Weather
import Foundation
public struct Forecast{
var lista:[Weather]
init(list: [Weather]) {
lista = list
}
}
public struct Weather{
let city:String
let timezone: String
let date: String
let clouds:String
let wind:String
let humidity:String
let temperature:String
let description:String
let icon:String
init(response:ResponseApi){
city = response.name
timezone = formatSecondsToHours(seconds: response.timezone)
date = convertToDate(timeZoneOffset: response.timezone)
clouds = "\(Int(response.clouds.all)) "
wind = "\(response.wind.speed)"
humidity = "\(response.main.humidity)"
temperature = "\(Int(response.main.temp))"
description = response.weather.first?.description ?? ""
icon = response.weather.first?.icon ?? ""
}
}
func formatSecondsToHours(seconds: Int) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "h:mm a"
let date = Date(timeIntervalSince1970: TimeInterval(seconds))
let formattedString = formatter.string(from: date)
return formattedString
}
func convertToDate(timeZoneOffset: Int) -> String {
let timezone = TimeZone(secondsFromGMT: timeZoneOffset)
let dateFormatter = DateFormatter()
dateFormatter.timeZone = timezone
dateFormatter.locale = Locale(identifier: "en_US")
dateFormatter.dateFormat = "EEEE, MMMM d yyyy"
let currentDateTime = Date()
let formattedDateTime = dateFormatter.string(from: currentDateTime)
return formattedDateTime.capitalized
}
WeatherViewModel
import Foundation
private let defaultIcon = "❌"
private let iconMap = [
"Drizzle" : "️",
"ThunderStorm" : "⛈️",
"Snow" : "❄️",
"Rain" : "️",
"Clear" : "☀️",
"Clouds" : "☁️"
]
class WeatherViewModel:ObservableObject{
@Published var cityname : String = "City Name"
@Published var timezone : String = "00:00 __"
@Published var date : String = "00/00/00"
@Published var cloudsProb:String = "0 %"
@Published var humidity:String = "0 %"
@Published var windSpeed:String = "0 km/h"
@Published var temperature : String = "__"
@Published var weatherDescription : String = "__"
@Published var weatherIcon : String = defaultIcon
public let weatherService: WeatherService
init(weatherService:WeatherService){
self.weatherService = weatherService
}
func refresh(){
weatherService.loadWeatherData{weather in
DispatchQueue.main.async {
self.cityname = weather.city
self.timezone = weather.timezone
self.date = weather.date
self.cloudsProb = "\(weather.clouds) %"
self.windSpeed = "\(weather.wind) km/h"
self.humidity = "\(weather.humidity) %"
self.temperature = "\(weather.temperature)°C"
self.weatherDescription = weather.description.capitalized
self.weatherIcon = iconMap[weather.icon] ?? defaultIcon
}
}
}
}
class ForecastViewModel:ObservableObject{
@Published var lista: [WeatherViewModel] = []
public let weatherService: WeatherService
init(weatherService:WeatherService){
self.weatherService = weatherService
}
func refreshForecast(){
weatherService.loadForecastData { forecast in
DispatchQueue.main.async { [self] in
let weatherViewModel = WeatherViewModel(weatherService: self.weatherService)
self.lista.append(weatherViewModel)
}
}
}
}
You cannot retrieve the 5 days forecast data from the api because the model structs you have do not match the json data you get from the server. Paste your url into your browser, eg:
then copy all of the json data into https://app.quicktype.io/, and all the correct struct models will be created for you.
Adjust them to your purpose, eg, add
Identifiableand all will work as it does for me in my tests.Also consult the docs https://openweathermap.org/forecast5#5days, to determine which properties are optionals, and add
?if required.Importantly, as mentioned, use
do/try/catcharound your decoding, and print the full error in thecatch.For example: