如何使用Swift UI从OpenWeatherMap获取5天天气预报

huangapple go评论65阅读模式
英文:

How to get the 5 days forecast from OpenWeatherMap using Swift Ui

问题

以下是您要翻译的代码部分:

// WeatherService

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()
    }
}
// Forecast

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

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)
                }
            }
    }
}

希望这些翻译能对您有所帮助。如果有其他问题,请随时提出。

英文:

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 = &quot;&lt;api-key&gt;&quot;
public var completionHandler: ((Weather) -&gt; Void)?
public var completionHandler2: ((Forecast) -&gt; Void)?
public override init(){
super.init()
locationManager.delegate = self
}
public func loadWeatherData(_ completionHandler: @escaping((Weather)-&gt;Void)) {
self.completionHandler = completionHandler
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
public func loadForecastData(_ completionHandler: @escaping((Forecast)-&gt;Void)) {
self.completionHandler2 = completionHandler
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
//api.openweathermap.org/data/2.5/forecast?lat=44.34&amp;lon=10.99&amp;appid={API key}
private func makeDataRequestForecast(forCoordinates coordinates: CLLocationCoordinate2D){
guard let urlString = &quot;https://api.openweathermap.org/data/2.5/forecast?lat=\(coordinates.latitude)&amp;lon=\(coordinates.longitude)&amp;appid=\(API_KEY)&amp;units=metric&quot;.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(&quot;error&quot;)
}
}.resume()
}
private func makeDataRequest(forCoordinates coordinates: CLLocationCoordinate2D){
guard let urlString = &quot;https://api.openweathermap.org/data/2.5/weather?lat=\(coordinates.latitude)&amp;lon=\(coordinates.longitude)&amp;appid=\(API_KEY)&amp;units=metric&quot;.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(&quot;error&quot;)
}
}.resume()
}
}
extension WeatherService: CLLocationManagerDelegate{
public func locationManager(
_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {return}
print(&quot;Location \(location.coordinate)&quot;)
makeDataRequest(forCoordinates: location.coordinate)
makeDataRequestForecast(forCoordinates: location.coordinate)
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(&quot;Error: \(error.localizedDescription)&quot;)
}
}
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 = &quot;main&quot;
}
}

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 = &quot;\(Int(response.clouds.all)) &quot;
wind = &quot;\(response.wind.speed)&quot;
humidity = &quot;\(response.main.humidity)&quot;
temperature = &quot;\(Int(response.main.temp))&quot;
description = response.weather.first?.description ?? &quot;&quot;
icon = response.weather.first?.icon ?? &quot;&quot;
}
}
func formatSecondsToHours(seconds: Int) -&gt; String {
let formatter = DateFormatter()
formatter.dateFormat = &quot;h:mm a&quot;
let date = Date(timeIntervalSince1970: TimeInterval(seconds))
let formattedString = formatter.string(from: date)
return formattedString
}
func convertToDate(timeZoneOffset: Int) -&gt; String {
let timezone = TimeZone(secondsFromGMT: timeZoneOffset)
let dateFormatter = DateFormatter()
dateFormatter.timeZone = timezone
dateFormatter.locale = Locale(identifier: &quot;en_US&quot;)
dateFormatter.dateFormat = &quot;EEEE, MMMM d yyyy&quot;
let currentDateTime = Date()
let formattedDateTime = dateFormatter.string(from: currentDateTime)
return formattedDateTime.capitalized
}

WeatherViewModel


import Foundation
private let defaultIcon = &quot;❌&quot;
private let iconMap = [
&quot;Drizzle&quot; : &quot;&#127782;️&quot;,
&quot;ThunderStorm&quot; : &quot;⛈️&quot;,
&quot;Snow&quot; : &quot;❄️&quot;,
&quot;Rain&quot; : &quot;&#127783;️&quot;,
&quot;Clear&quot; : &quot;☀️&quot;,
&quot;Clouds&quot; : &quot;☁️&quot;
]
class WeatherViewModel:ObservableObject{
@Published var cityname : String = &quot;City Name&quot;
@Published var timezone : String = &quot;00:00 __&quot;
@Published var date : String = &quot;00/00/00&quot;
@Published var cloudsProb:String = &quot;0 %&quot;
@Published var humidity:String = &quot;0 %&quot;
@Published var windSpeed:String = &quot;0 km/h&quot;
@Published var temperature : String = &quot;__&quot;
@Published var weatherDescription : String = &quot;__&quot;
@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 = &quot;\(weather.clouds) %&quot;
self.windSpeed = &quot;\(weather.wind) km/h&quot;
self.humidity = &quot;\(weather.humidity) %&quot;
self.temperature = &quot;\(weather.temperature)&#176;C&quot;
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)
}
}
}
}

答案1

得分: 1

以下是您要翻译的内容:

"你无法从API中检索到5天的天气预报数据,因为您拥有的模型结构与您从服务器获取的JSON数据不匹配。将您的URL粘贴到浏览器中,例如:

https://api.openweathermap.org/data/2.5/forecast?lat=44.34&lon=10.99&appid={API密钥}

然后将所有的JSON数据复制到https://app.quicktype.io/中,所有正确的结构模型将为您创建。

根据您的需要进行调整,例如,添加Identifiable,所有东西都会像我在我的测试中一样工作。

还要查阅文档https://openweathermap.org/forecast5#5days,确定哪些属性是可选的,如果需要的话,添加?

重要的是,如上所述,使用do/try/catch来处理解码,将完整的错误打印在catch中。

例如:

struct ForecastApi: Codable {
    let cod: String
    let message, cnt: Int
    let list: [ListResponse]
    let city: City
}

struct City: Codable {
    let id: Int
    let name: String
    let coord: Coord
    let country: String
    let population, timezone, sunrise, sunset: Int
}

struct Coord: Codable {
    let lat, lon: Double
}

struct ListResponse: Identifiable, Codable {
    let id = UUID()
    let dt: Int
    let main: MainClass
    let weather: [Weather]
    let clouds: Clouds
    let wind: Wind
    let visibility: Int
    let pop: Double
    let sys: Sys
    let dtTxt: String
    let rain: Rain?

    enum CodingKeys: String, CodingKey {
        case dt, main, weather, clouds, wind, visibility, pop, sys, rain
        case dtTxt = "dt_txt"
    }
}

struct Clouds: Codable {
    let all: Int
}

struct MainClass: Codable {
    let temp, feelsLike, tempMin, tempMax: Double
    let pressure, seaLevel, grndLevel, humidity: Int
    let tempKf: Double

    enum CodingKeys: String, CodingKey {
        case temp
        case feelsLike = "feels_like"
        case tempMin = "temp_min"
        case tempMax = "temp_max"
        case pressure
        case seaLevel = "sea_level"
        case grndLevel = "grnd_level"
        case humidity
        case tempKf = "temp_kf"
    }
}

struct Rain: Codable {
    let the3H: Double

    enum CodingKeys: String, CodingKey {
        case the3H = "3h"
    }
}

struct Sys: Codable {
    let pod: Pod
}

enum Pod: String, Codable {
    case d = "d"
    case n = "n"
}

struct Weather: Codable {
    let id: Int
    let main: String
    let description: String
    let icon: String
}

enum Description: String, Codable {
    case brokenClouds = "broken clouds"
    case fewClouds = "few clouds"
    case lightRain = "light rain"
    case moderateRain = "moderate rain"
    case overcastClouds = "overcast clouds"
    case scatteredClouds = "scattered clouds"
}

enum MainEnum: String, Codable {
    case clouds = "Clouds"
    case rain = "Rain"
}

struct Wind: Codable {
    let speed: Double
    let deg: Int
    let gust: Double
}
英文:

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:

    https://api.openweathermap.org/data/2.5/forecast?lat=44.34&amp;lon=10.99&amp;appid={API key}

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 Identifiable and 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/catch around your decoding, and print the full error in the catch.

For example:

 struct ForecastApi: Codable {
let cod: String
let message, cnt: Int
let list: [ListResponse]
let city: City
}
struct City: Codable {
let id: Int
let name: String
let coord: Coord
let country: String
let population, timezone, sunrise, sunset: Int
}
struct Coord: Codable {
let lat, lon: Double
}
struct ListResponse: Identifiable, Codable {
let id = UUID()
let dt: Int
let main: MainClass
let weather: [Weather]
let clouds: Clouds
let wind: Wind
let visibility: Int
let pop: Double
let sys: Sys
let dtTxt: String
let rain: Rain?
enum CodingKeys: String, CodingKey {
case dt, main, weather, clouds, wind, visibility, pop, sys, rain
case dtTxt = &quot;dt_txt&quot;
}
}
struct Clouds: Codable {
let all: Int
}
struct MainClass: Codable {
let temp, feelsLike, tempMin, tempMax: Double
let pressure, seaLevel, grndLevel, humidity: Int
let tempKf: Double
enum CodingKeys: String, CodingKey {
case temp
case feelsLike = &quot;feels_like&quot;
case tempMin = &quot;temp_min&quot;
case tempMax = &quot;temp_max&quot;
case pressure
case seaLevel = &quot;sea_level&quot;
case grndLevel = &quot;grnd_level&quot;
case humidity
case tempKf = &quot;temp_kf&quot;
}
}
struct Rain: Codable {
let the3H: Double
enum CodingKeys: String, CodingKey {
case the3H = &quot;3h&quot;
}
}
struct Sys: Codable {
let pod: Pod
}
enum Pod: String, Codable {
case d = &quot;d&quot;
case n = &quot;n&quot;
}
struct Weather: Codable {
let id: Int
let main: String
let description: String
let icon: String
}
enum Description: String, Codable {
case brokenClouds = &quot;broken clouds&quot;
case fewClouds = &quot;few clouds&quot;
case lightRain = &quot;light rain&quot;
case moderateRain = &quot;moderate rain&quot;
case overcastClouds = &quot;overcast clouds&quot;
case scatteredClouds = &quot;scattered clouds&quot;
}
enum MainEnum: String, Codable {
case clouds = &quot;Clouds&quot;
case rain = &quot;Rain&quot;
}
struct Wind: Codable {
let speed: Double
let deg: Int
let gust: Double
}

huangapple
  • 本文由 发表于 2023年6月1日 03:05:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76376565.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定