英文:
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 = "<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)
}
}
}
}
答案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&lon=10.99&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 = "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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论