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

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

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

问题

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

  1. // WeatherService
  2. public final class WeatherService: NSObject {
  3. public let locationManager = CLLocationManager()
  4. private let API_KEY = "<api-key>"
  5. public var completionHandler: ((Weather) -> Void)?
  6. public var completionHandler2: ((Forecast) -> Void)?
  7. public override init(){
  8. super.init()
  9. locationManager.delegate = self
  10. }
  11. public func loadWeatherData(_ completionHandler: @escaping((Weather)->Void)) {
  12. self.completionHandler = completionHandler
  13. locationManager.requestWhenInUseAuthorization()
  14. locationManager.startUpdatingLocation()
  15. }
  16. public func loadForecastData(_ completionHandler: @escaping((Forecast)->Void)) {
  17. self.completionHandler2 = completionHandler
  18. locationManager.requestWhenInUseAuthorization()
  19. locationManager.startUpdatingLocation()
  20. }
  21. //api.openweathermap.org/data/2.5/forecast?lat=44.34&lon=10.99&appid={API key}
  22. private func makeDataRequestForecast(forCoordinates coordinates: CLLocationCoordinate2D){
  23. 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}
  24. guard let url = URL(string: urlString) else {return}
  25. URLSession.shared.dataTask(with: url){ data, response, error in
  26. guard error == nil,let data = data else {return}
  27. if let response = try? JSONDecoder().decode(ForecastApi.self, from: data) {
  28. let weatherList = response.list.map { Weather(response: $0) }
  29. let forecast = Forecast(list: weatherList)
  30. print(response)
  31. self.completionHandler2?(forecast)
  32. print(response)
  33. }else{
  34. print("error")
  35. }
  36. }.resume()
  37. }
  38. private func makeDataRequest(forCoordinates coordinates: CLLocationCoordinate2D){
  39. 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}
  40. guard let url = URL(string: urlString) else {return}
  41. URLSession.shared.dataTask(with: url){ data, response, error in
  42. guard error == nil,let data = data else {return}
  43. if let response = try? JSONDecoder().decode(ResponseApi.self, from: data){
  44. let weather = Weather(response: response)
  45. self.completionHandler?(weather)
  46. }else{
  47. print("error")
  48. }
  49. }.resume()
  50. }
  51. }
  1. // Forecast
  2. public struct Forecast{
  3. var lista:[Weather]
  4. init(list: [Weather]) {
  5. lista = list
  6. }
  7. }
  8. public struct Weather{
  9. let city:String
  10. let timezone: String
  11. let date: String
  12. let clouds:String
  13. let wind:String
  14. let humidity:String
  15. let temperature:String
  16. let description:String
  17. let icon:String
  18. init(response:ResponseApi){
  19. city = response.name
  20. timezone = formatSecondsToHours(seconds: response.timezone)
  21. date = convertToDate(timeZoneOffset: response.timezone)
  22. clouds = "\(Int(response.clouds.all)) "
  23. wind = "\(response.wind.speed)"
  24. humidity = "\(response.main.humidity)"
  25. temperature = "\(Int(response.main.temp))"
  26. description = response.weather.first?.description ?? ""
  27. icon = response.weather.first?.icon ?? ""
  28. }
  29. }
  30. func formatSecondsToHours(seconds: Int) -> String {
  31. let formatter = DateFormatter()
  32. formatter.dateFormat = "h:mm a"
  33. let date = Date(timeIntervalSince1970: TimeInterval(seconds))
  34. let formattedString = formatter.string(from: date)
  35. return formattedString
  36. }
  37. func convertToDate(timeZoneOffset: Int) -> String {
  38. let timezone = TimeZone(secondsFromGMT: timeZoneOffset)
  39. let dateFormatter = DateFormatter()
  40. dateFormatter.timeZone = timezone
  41. dateFormatter.locale = Locale(identifier: "en_US")
  42. dateFormatter.dateFormat = "EEEE, MMMM d yyyy"
  43. let currentDateTime = Date()
  44. let formattedDateTime = dateFormatter.string(from: currentDateTime)
  45. return formattedDateTime.capitalized
  46. }
  1. // WeatherViewModel
  2. private let defaultIcon = "❌"
  3. private let iconMap = [
  4. "Drizzle" : "🌧️",
  5. "ThunderStorm" : "⛈️",
  6. "Snow" : "❄️",
  7. "Rain" : "🌦️",
  8. "Clear" : "☀️",
  9. "Clouds" : "☁️"
  10. ]
  11. class WeatherViewModel:ObservableObject{
  12. @Published var cityname : String = "City Name"
  13. @Published var timezone : String = "00:00 __"
  14. @Published var date : String = "00/00/00"
  15. @Published var cloudsProb:String = "0 %"
  16. @Published var humidity:String = "0 %"
  17. @Published var windSpeed:String = "0 km/h"
  18. @Published var temperature : String = "__"
  19. @Published var weatherDescription : String = "__"
  20. @Published var weatherIcon : String = defaultIcon
  21. public let weatherService: WeatherService
  22. init(weatherService:WeatherService){
  23. self.weatherService = weatherService
  24. }
  25. func refresh(){
  26. weatherService.loadWeatherData{weather in
  27. DispatchQueue.main.async {
  28. self.cityname = weather.city
  29. self.timezone = weather.timezone
  30. self.date = weather.date
  31. self.cloudsProb = "\(weather.clouds) %"
  32. self.windSpeed = "\(weather.wind) km/h"
  33. self.humidity = "\(weather.humidity) %"
  34. self.temperature = "\(weather.temperature)°C"
  35. self.weatherDescription = weather.description.capitalized
  36. self.weatherIcon = iconMap[weather.icon] ?? defaultIcon
  37. }
  38. }
  39. }
  40. }
  41. class ForecastViewModel:ObservableObject{
  42. @Published var lista: [WeatherViewModel] = []
  43. public let weatherService: WeatherService
  44. init(weatherService:WeatherService){
  45. self.weatherService = weatherService
  46. }
  47. func refreshForecast(){
  48. weatherService.loadForecastData { forecast in
  49. DispatchQueue.main.async { [self] in
  50. let weatherViewModel = WeatherViewModel(weatherService: self.weatherService)
  51. self.lista.append(weatherViewModel)
  52. }
  53. }
  54. }
  55. }

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

英文:

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

  1. import CoreLocation
  2. import Foundation
  3. public final class WeatherService: NSObject {
  4. public let locationManager = CLLocationManager()
  5. private let API_KEY = &quot;&lt;api-key&gt;&quot;
  6. public var completionHandler: ((Weather) -&gt; Void)?
  7. public var completionHandler2: ((Forecast) -&gt; Void)?
  8. public override init(){
  9. super.init()
  10. locationManager.delegate = self
  11. }
  12. public func loadWeatherData(_ completionHandler: @escaping((Weather)-&gt;Void)) {
  13. self.completionHandler = completionHandler
  14. locationManager.requestWhenInUseAuthorization()
  15. locationManager.startUpdatingLocation()
  16. }
  17. public func loadForecastData(_ completionHandler: @escaping((Forecast)-&gt;Void)) {
  18. self.completionHandler2 = completionHandler
  19. locationManager.requestWhenInUseAuthorization()
  20. locationManager.startUpdatingLocation()
  21. }
  22. //api.openweathermap.org/data/2.5/forecast?lat=44.34&amp;lon=10.99&amp;appid={API key}
  23. private func makeDataRequestForecast(forCoordinates coordinates: CLLocationCoordinate2D){
  24. 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}
  25. guard let url = URL(string: urlString) else {return}
  26. URLSession.shared.dataTask(with: url){ data, response, error in
  27. guard error == nil,let data = data else {return}
  28. if let response = try? JSONDecoder().decode(ForecastApi.self, from: data) {
  29. let weatherList = response.list.map { Weather(response: $0) }
  30. let forecast = Forecast(list: weatherList)
  31. print(response)
  32. self.completionHandler2?(forecast)
  33. print(response)
  34. }else{
  35. print(&quot;error&quot;)
  36. }
  37. }.resume()
  38. }
  39. private func makeDataRequest(forCoordinates coordinates: CLLocationCoordinate2D){
  40. 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}
  41. guard let url = URL(string: urlString) else {return}
  42. URLSession.shared.dataTask(with: url){ data, response, error in
  43. guard error == nil,let data = data else {return}
  44. if let response = try? JSONDecoder().decode(ResponseApi.self, from: data){
  45. let weather = Weather(response: response)
  46. self.completionHandler?(weather)
  47. }else{
  48. print(&quot;error&quot;)
  49. }
  50. }.resume()
  51. }
  52. }
  53. extension WeatherService: CLLocationManagerDelegate{
  54. public func locationManager(
  55. _ manager: CLLocationManager,
  56. didUpdateLocations locations: [CLLocation]) {
  57. guard let location = locations.first else {return}
  58. print(&quot;Location \(location.coordinate)&quot;)
  59. makeDataRequest(forCoordinates: location.coordinate)
  60. makeDataRequestForecast(forCoordinates: location.coordinate)
  61. }
  62. public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
  63. print(&quot;Error: \(error.localizedDescription)&quot;)
  64. }
  65. }
  66. struct ForecastApi:Decodable{
  67. let list:[ResponseApi]
  68. }
  69. struct ResponseApi:Decodable{
  70. let name:String
  71. let timezone:Int
  72. let wind:Wind
  73. let clouds:Clouds
  74. let main: MainApi
  75. let weather:[WeatherApi]
  76. }
  77. struct Wind:Decodable{
  78. let speed:Double
  79. }
  80. struct Clouds:Decodable{
  81. let all:Double
  82. }
  83. struct MainApi:Decodable{
  84. let temp: Double
  85. let humidity:Int
  86. }
  87. struct WeatherApi : Decodable{
  88. let description:String
  89. let icon:String
  90. enum CodingKeys: String,CodingKey{
  91. case description
  92. case icon = &quot;main&quot;
  93. }
  94. }

Weather

  1. import Foundation
  2. public struct Forecast{
  3. var lista:[Weather]
  4. init(list: [Weather]) {
  5. lista = list
  6. }
  7. }
  8. public struct Weather{
  9. let city:String
  10. let timezone: String
  11. let date: String
  12. let clouds:String
  13. let wind:String
  14. let humidity:String
  15. let temperature:String
  16. let description:String
  17. let icon:String
  18. init(response:ResponseApi){
  19. city = response.name
  20. timezone = formatSecondsToHours(seconds: response.timezone)
  21. date = convertToDate(timeZoneOffset: response.timezone)
  22. clouds = &quot;\(Int(response.clouds.all)) &quot;
  23. wind = &quot;\(response.wind.speed)&quot;
  24. humidity = &quot;\(response.main.humidity)&quot;
  25. temperature = &quot;\(Int(response.main.temp))&quot;
  26. description = response.weather.first?.description ?? &quot;&quot;
  27. icon = response.weather.first?.icon ?? &quot;&quot;
  28. }
  29. }
  30. func formatSecondsToHours(seconds: Int) -&gt; String {
  31. let formatter = DateFormatter()
  32. formatter.dateFormat = &quot;h:mm a&quot;
  33. let date = Date(timeIntervalSince1970: TimeInterval(seconds))
  34. let formattedString = formatter.string(from: date)
  35. return formattedString
  36. }
  37. func convertToDate(timeZoneOffset: Int) -&gt; String {
  38. let timezone = TimeZone(secondsFromGMT: timeZoneOffset)
  39. let dateFormatter = DateFormatter()
  40. dateFormatter.timeZone = timezone
  41. dateFormatter.locale = Locale(identifier: &quot;en_US&quot;)
  42. dateFormatter.dateFormat = &quot;EEEE, MMMM d yyyy&quot;
  43. let currentDateTime = Date()
  44. let formattedDateTime = dateFormatter.string(from: currentDateTime)
  45. return formattedDateTime.capitalized
  46. }

WeatherViewModel

  1. import Foundation
  2. private let defaultIcon = &quot;❌&quot;
  3. private let iconMap = [
  4. &quot;Drizzle&quot; : &quot;&#127782;️&quot;,
  5. &quot;ThunderStorm&quot; : &quot;⛈️&quot;,
  6. &quot;Snow&quot; : &quot;❄️&quot;,
  7. &quot;Rain&quot; : &quot;&#127783;️&quot;,
  8. &quot;Clear&quot; : &quot;☀️&quot;,
  9. &quot;Clouds&quot; : &quot;☁️&quot;
  10. ]
  11. class WeatherViewModel:ObservableObject{
  12. @Published var cityname : String = &quot;City Name&quot;
  13. @Published var timezone : String = &quot;00:00 __&quot;
  14. @Published var date : String = &quot;00/00/00&quot;
  15. @Published var cloudsProb:String = &quot;0 %&quot;
  16. @Published var humidity:String = &quot;0 %&quot;
  17. @Published var windSpeed:String = &quot;0 km/h&quot;
  18. @Published var temperature : String = &quot;__&quot;
  19. @Published var weatherDescription : String = &quot;__&quot;
  20. @Published var weatherIcon : String = defaultIcon
  21. public let weatherService: WeatherService
  22. init(weatherService:WeatherService){
  23. self.weatherService = weatherService
  24. }
  25. func refresh(){
  26. weatherService.loadWeatherData{weather in
  27. DispatchQueue.main.async {
  28. self.cityname = weather.city
  29. self.timezone = weather.timezone
  30. self.date = weather.date
  31. self.cloudsProb = &quot;\(weather.clouds) %&quot;
  32. self.windSpeed = &quot;\(weather.wind) km/h&quot;
  33. self.humidity = &quot;\(weather.humidity) %&quot;
  34. self.temperature = &quot;\(weather.temperature)&#176;C&quot;
  35. self.weatherDescription = weather.description.capitalized
  36. self.weatherIcon = iconMap[weather.icon] ?? defaultIcon
  37. }
  38. }
  39. }
  40. }
  41. class ForecastViewModel:ObservableObject{
  42. @Published var lista: [WeatherViewModel] = []
  43. public let weatherService: WeatherService
  44. init(weatherService:WeatherService){
  45. self.weatherService = weatherService
  46. }
  47. func refreshForecast(){
  48. weatherService.loadForecastData { forecast in
  49. DispatchQueue.main.async { [self] in
  50. let weatherViewModel = WeatherViewModel(weatherService: self.weatherService)
  51. self.lista.append(weatherViewModel)
  52. }
  53. }
  54. }
  55. }

答案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中。

例如:

  1. struct ForecastApi: Codable {
  2. let cod: String
  3. let message, cnt: Int
  4. let list: [ListResponse]
  5. let city: City
  6. }
  7. struct City: Codable {
  8. let id: Int
  9. let name: String
  10. let coord: Coord
  11. let country: String
  12. let population, timezone, sunrise, sunset: Int
  13. }
  14. struct Coord: Codable {
  15. let lat, lon: Double
  16. }
  17. struct ListResponse: Identifiable, Codable {
  18. let id = UUID()
  19. let dt: Int
  20. let main: MainClass
  21. let weather: [Weather]
  22. let clouds: Clouds
  23. let wind: Wind
  24. let visibility: Int
  25. let pop: Double
  26. let sys: Sys
  27. let dtTxt: String
  28. let rain: Rain?
  29. enum CodingKeys: String, CodingKey {
  30. case dt, main, weather, clouds, wind, visibility, pop, sys, rain
  31. case dtTxt = "dt_txt"
  32. }
  33. }
  34. struct Clouds: Codable {
  35. let all: Int
  36. }
  37. struct MainClass: Codable {
  38. let temp, feelsLike, tempMin, tempMax: Double
  39. let pressure, seaLevel, grndLevel, humidity: Int
  40. let tempKf: Double
  41. enum CodingKeys: String, CodingKey {
  42. case temp
  43. case feelsLike = "feels_like"
  44. case tempMin = "temp_min"
  45. case tempMax = "temp_max"
  46. case pressure
  47. case seaLevel = "sea_level"
  48. case grndLevel = "grnd_level"
  49. case humidity
  50. case tempKf = "temp_kf"
  51. }
  52. }
  53. struct Rain: Codable {
  54. let the3H: Double
  55. enum CodingKeys: String, CodingKey {
  56. case the3H = "3h"
  57. }
  58. }
  59. struct Sys: Codable {
  60. let pod: Pod
  61. }
  62. enum Pod: String, Codable {
  63. case d = "d"
  64. case n = "n"
  65. }
  66. struct Weather: Codable {
  67. let id: Int
  68. let main: String
  69. let description: String
  70. let icon: String
  71. }
  72. enum Description: String, Codable {
  73. case brokenClouds = "broken clouds"
  74. case fewClouds = "few clouds"
  75. case lightRain = "light rain"
  76. case moderateRain = "moderate rain"
  77. case overcastClouds = "overcast clouds"
  78. case scatteredClouds = "scattered clouds"
  79. }
  80. enum MainEnum: String, Codable {
  81. case clouds = "Clouds"
  82. case rain = "Rain"
  83. }
  84. struct Wind: Codable {
  85. let speed: Double
  86. let deg: Int
  87. let gust: Double
  88. }
英文:

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:

  1. 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:

  1. struct ForecastApi: Codable {
  2. let cod: String
  3. let message, cnt: Int
  4. let list: [ListResponse]
  5. let city: City
  6. }
  7. struct City: Codable {
  8. let id: Int
  9. let name: String
  10. let coord: Coord
  11. let country: String
  12. let population, timezone, sunrise, sunset: Int
  13. }
  14. struct Coord: Codable {
  15. let lat, lon: Double
  16. }
  17. struct ListResponse: Identifiable, Codable {
  18. let id = UUID()
  19. let dt: Int
  20. let main: MainClass
  21. let weather: [Weather]
  22. let clouds: Clouds
  23. let wind: Wind
  24. let visibility: Int
  25. let pop: Double
  26. let sys: Sys
  27. let dtTxt: String
  28. let rain: Rain?
  29. enum CodingKeys: String, CodingKey {
  30. case dt, main, weather, clouds, wind, visibility, pop, sys, rain
  31. case dtTxt = &quot;dt_txt&quot;
  32. }
  33. }
  34. struct Clouds: Codable {
  35. let all: Int
  36. }
  37. struct MainClass: Codable {
  38. let temp, feelsLike, tempMin, tempMax: Double
  39. let pressure, seaLevel, grndLevel, humidity: Int
  40. let tempKf: Double
  41. enum CodingKeys: String, CodingKey {
  42. case temp
  43. case feelsLike = &quot;feels_like&quot;
  44. case tempMin = &quot;temp_min&quot;
  45. case tempMax = &quot;temp_max&quot;
  46. case pressure
  47. case seaLevel = &quot;sea_level&quot;
  48. case grndLevel = &quot;grnd_level&quot;
  49. case humidity
  50. case tempKf = &quot;temp_kf&quot;
  51. }
  52. }
  53. struct Rain: Codable {
  54. let the3H: Double
  55. enum CodingKeys: String, CodingKey {
  56. case the3H = &quot;3h&quot;
  57. }
  58. }
  59. struct Sys: Codable {
  60. let pod: Pod
  61. }
  62. enum Pod: String, Codable {
  63. case d = &quot;d&quot;
  64. case n = &quot;n&quot;
  65. }
  66. struct Weather: Codable {
  67. let id: Int
  68. let main: String
  69. let description: String
  70. let icon: String
  71. }
  72. enum Description: String, Codable {
  73. case brokenClouds = &quot;broken clouds&quot;
  74. case fewClouds = &quot;few clouds&quot;
  75. case lightRain = &quot;light rain&quot;
  76. case moderateRain = &quot;moderate rain&quot;
  77. case overcastClouds = &quot;overcast clouds&quot;
  78. case scatteredClouds = &quot;scattered clouds&quot;
  79. }
  80. enum MainEnum: String, Codable {
  81. case clouds = &quot;Clouds&quot;
  82. case rain = &quot;Rain&quot;
  83. }
  84. struct Wind: Codable {
  85. let speed: Double
  86. let deg: Int
  87. let gust: Double
  88. }

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:

确定