【Swift】VisionOS で天気アプリを作ってみる Part 2
初めに
今回は、前回に引き続き Open-Meteo API を使って天気情報を取得し、その情報によってUIを切り替える天気アプリを実装していきたいと思います。
Part 2 の今回は、複数地点の天気情報を取得したり、取得したデータをグラフにしたりしてみましょう。
今回の記事は前回の Part 1 の続きになるので、一連の流れを掴む上でも以下の記事を参照して貰えばと思います。
今回扱う技術トピックは以下のようになるかと思います。
- APIからのデータ取得
- NavigationSplitView
- Imageの切り替え
記事の対象者
- Swift, SwiftUI 学習者
- Vision OS に触れてみたい方
- Swift で API の実装をしてみたい方
完成イメージ
全体の完成イメージ
今回は日本各地の天気情報を取得し、そのデータに対応した画像を背景画像するビューを作成してみましょう。
今回の完成イメージ
実装
前回までの実装
前回のコード全文
import SwiftUI
@main
struct WeatherApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
import SwiftUI
struct ContentView: View {
@ObservedObject var weatherViewModel = WeatherViewModel()
let tokyoLat = 35.6895
let tokyoLon = 139.6917
let tokyoTimeZone = "Asia%2FTokyo"
var body: some View {
VStack {
Text("東京の天気")
.font(.title)
if (weatherViewModel.weatherData != nil) {
HStack {
Text("気温")
Text("\(Int(weatherViewModel.weatherData!.current.temperature2M))℃")
}
HStack {
Text("降水量")
Text("\(Int(weatherViewModel.weatherData!.current.rain))")
}
} else {
Text("天気を取得...")
}
}
.onAppear {
weatherViewModel.fetchWeatherData(latitude: String(tokyoLat), longitude: String(tokyoLon), timeZone: tokyoTimeZone)
}
}
}
import Foundation
// MARK: - Weather
struct Weather: Codable {
let latitude, longitude, generationtimeMS: Double
let utcOffsetSeconds: Int
let timezone, timezoneAbbreviation: String
let elevation: Int
let currentUnits: Units
let current: Current
let hourlyUnits: Units
let hourly: Hourly
let dailyUnits: DailyUnits
let daily: Daily
enum CodingKeys: String, CodingKey {
case latitude, longitude
case generationtimeMS = "generationtime_ms"
case utcOffsetSeconds = "utc_offset_seconds"
case timezone
case timezoneAbbreviation = "timezone_abbreviation"
case elevation
case currentUnits = "current_units"
case current
case hourlyUnits = "hourly_units"
case hourly
case dailyUnits = "daily_units"
case daily
}
}
// MARK: - Current
struct Current: Codable {
let time: String
let interval: Int
let temperature2M: Double
let rain, weatherCode: Int
enum CodingKeys: String, CodingKey {
case time, interval
case temperature2M = "temperature_2m"
case rain
case weatherCode = "weather_code"
}
}
// MARK: - Units
struct Units: Codable {
let time: String
let interval: String?
let temperature2M, rain, weatherCode: String
enum CodingKeys: String, CodingKey {
case time, interval
case temperature2M = "temperature_2m"
case rain
case weatherCode = "weather_code"
}
}
// MARK: - Daily
struct Daily: Codable {
let time: [String]
let weatherCode: [Int]
let temperature2MMax, temperature2MMin: [Double]
enum CodingKeys: String, CodingKey {
case time
case weatherCode = "weather_code"
case temperature2MMax = "temperature_2m_max"
case temperature2MMin = "temperature_2m_min"
}
}
// MARK: - DailyUnits
struct DailyUnits: Codable {
let time, weatherCode, temperature2MMax, temperature2MMin: String
enum CodingKeys: String, CodingKey {
case time
case weatherCode = "weather_code"
case temperature2MMax = "temperature_2m_max"
case temperature2MMin = "temperature_2m_min"
}
}
// MARK: - Hourly
struct Hourly: Codable {
let time: [String]
let temperature2M: [Double]
let rain, weatherCode: [Int]
enum CodingKeys: String, CodingKey {
case time
case temperature2M = "temperature_2m"
case rain
case weatherCode = "weather_code"
}
}
import SwiftUI
import Foundation
class WeatherViewModel: ObservableObject {
@Published var weatherData: Weather?
func fetchWeatherData(latitude: String, longitude: String, timeZone: String) {
let urlString = "https://api.open-meteo.com/v1/forecast?latitude=\(latitude)&longitude=\(longitude)¤t=temperature_2m,rain,weather_code&hourly=temperature_2m,rain,weather_code&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=\(timeZone)"
guard let url = URL(string: urlString) else {
print("Invalid URL")
return
}
URLSession.shared.dataTask(with: url) { data, _, error in
DispatchQueue.main.async {
if let data = data {
do {
let decodedData = try JSONDecoder().decode(Weather.self, from: data)
self.weatherData = decodedData
} catch {
print("Error decoding: \(error)")
}
} else if let error = error {
print("Error fetching data: \(error)")
}
}
}.resume()
}
}
前回は、以下のように東京の天気情報を取得し、それを表示するまで実装しました。
複数都市のリスト表示
前回は東京の天気情報のみを取得しましたが、今回は以下の都市のデータを取得して表示してみましょう。
- 東京
- 大阪
- 名古屋
- 北海道
- 沖縄
City Model
まずは、それぞれの都市のモデルを作成します。
都市のモデルは以下のようになります。
import Foundation
struct City: Codable, Identifiable, Hashable {
var id: String { name }
let name: String
let latitude: Double
let longitude: Double
let timeZone: String
enum CodingKeys: String, CodingKey {
case name, latitude, longitude
case timeZone = "time_zone"
}
}
City
は都市名に加えて、天気情報を取得ために必要な緯度・経度・タイムゾーンを持っています。
上記の実装だと id
として name
を持っているため、同一の名前の都市が登録された場合は id
が重複してエラーになるかと思いますが、今回登録する都市は少数であるため、ひとまずこの実装で行います。
次に、 City
にサンプルとして使用する以下の都市の情報も追加しておきます。
let sampleCities = [
City(name: "Tokyo", latitude: 35.6895, longitude: 139.6917, timeZone: "Asia%2FTokyo"),
City(name: "Osaka", latitude: 34.686, longitude: 135.520, timeZone: "Asia%2FTokyo"),
City(name: "Nagoya", latitude: 35.1709, longitude: 136.8815, timeZone: "Asia%2FTokyo"),
City(name: "Hokkaido", latitude: 43.0614, longitude: 141.3538, timeZone: "Asia%2FTokyo"),
City(name: "Okinawa", latitude: 26.2123, longitude: 127.6790, timeZone: "Asia%2FTokyo"),
]
次に ContentView
で都市のリストを実装してみます。
コードは以下のようになります。
import SwiftUI
struct ContentView: View {
@ObservedObject var weatherViewModel = WeatherViewModel()
@State private var selectedCity: City?
init() {
_selectedCity = State(initialValue: sampleCities[0])
}
var body: some View {
NavigationSplitView {
List(sampleCities, id: \.self, selection: $selectedCity) { city in
Text(city.name)
}
.navigationTitle("City")
} detail: {
if let selectedCity {
VStack {
Text(selectedCity.name)
.font(.system(size: 30))
}
.navigationTitle("Weather")
}
}
}
}
詳しくみてみましょう。
まずは以下のコードで選択中の都市の情報を持つ selectedCity
を定義しています。
この値の State が変更されることで、表示される都市の名前も変更されるようになります。
@State private var selectedCity: City?
以下のコードでは、初期化処理として、sampleCities
の一番初めの都市(この場合では東京)を_selectedCity
に指定しています。したがってこのビューが表示されるときは東京が選択された状態で表示されます。
init() {
_selectedCity = State(initialValue: sampleCities[0])
}
以下のコードでは NavigationSplitView
を使って都市のリストと都市名を表示しています。
detail
を指定することで、ビューの左側にコンテンツを表示できるようになります。
今の状態では都市名を表示しているだけですが、これから各都市の天気情報を表示させていきます。
NavigationSplitView {
List(sampleCities, id: \.self, selection: $selectedCity) { city in
Text(city.name)
}
.navigationTitle("City")
} detail: {
if let selectedCity {
VStack {
Text(selectedCity.name)
.font(.system(size: 30))
}
.navigationTitle("Weather")
}
}
実行してみると以下のように、都市のリストと対応する名前が左側に表示されることがわかります。
複数都市の天気情報表示
次はリストで表示した都市の天気情報を取得し、表示させるところまで実装していきます。
先に変更する ContentView
の全コードを提示します。
import SwiftUI
struct ContentView: View {
@ObservedObject var weatherViewModel = WeatherViewModel()
@State private var selectedCity: City?
init() {
_selectedCity = State(initialValue: sampleCities[0])
}
var body: some View {
NavigationSplitView {
List(sampleCities, id: \.self, selection: $selectedCity) { city in
Text(city.name)
}
.onChange(of: selectedCity) {
weatherViewModel.fetchWeatherData(
latitude: String(selectedCity!.latitude),
longitude: String(selectedCity!.longitude),
timeZone: selectedCity!.timeZone
)
}
.navigationTitle("City")
} detail: {
if let selectedCity {
VStack {
Text(selectedCity.name)
.font(.system(size: 30))
if (weatherViewModel.weatherData != nil) {
Text("\(Int(weatherViewModel.weatherData!.current.temperature2M))°").font(.system(size: 60))
HStack {
VStack {
Text("最高気温")
.font(.system(size: 30))
Text("\(Int(weatherViewModel.weatherData!.daily.temperature2MMax[0]))°").font(.system(size: 50))
}
VStack {
Text("最低気温")
.font(.system(size: 30))
Text("\(Int(weatherViewModel.weatherData!.daily.temperature2MMin[0]))°").font(.system(size: 50))
}
}
.padding(10)
Text("\(weatherDescription(from: weatherViewModel.weatherData!.current.weatherCode))").font(.system(size: 40))
} else {
Text("天気情報取得 ...")
}
}
.navigationTitle("Weather")
}
}
}
}
変更した部分に関して解説していきます。
まずは以下の部分です。
リストに対して、onChange
を設定し、of
には selectedCity
を指定しています。
こうすることで、selectedCity
の値つまり選択されている都市が変更された場合、再度 fetchWeatherData
を実行し、Open-Meteo API から選択されている都市の天気情報を取得しなおすようにしています。
.onChange(of: selectedCity) {
weatherViewModel.fetchWeatherData(
latitude: String(selectedCity!.latitude),
longitude: String(selectedCity!.longitude),
timeZone: selectedCity!.timeZone
)
}
以下の部分では、少し長いですが、以下の四項目を表示させています。
- 現在の気温
- 今日の最高気温
- 今日の最低気温
- 現在の天気
if (weatherViewModel.weatherData != nil) {
Text("\(Int(weatherViewModel.weatherData!.current.temperature2M))°")
.font(.system(size: 60))
HStack {
VStack {
Text("最高気温")
.font(.system(size: 30))
Text("\(Int(weatherViewModel.weatherData!.daily.temperature2MMax[0]))°")
.font(.system(size: 50))
}
VStack {
Text("最低気温")
.font(.system(size: 30))
Text("\(Int(weatherViewModel.weatherData!.daily.temperature2MMin[0]))°")
.font(.system(size: 50))
}
}
.padding(10)
Text("\(weatherDescription(from: weatherViewModel.weatherData!.current.weatherCode))").font(.system(size: 40))
} else {
Text("天気情報取得 ...")
}
ここで詳しく解説したいのは、「現在の天気」についてです。
Open-Meteo API では、天気は String ではなく、Int の天気コードとして返却されます。
したがって、返却された天気コードを意味のある文字列に変換する必要があります。今回は以下のようなコードを Weather
ファイルに追加します。
func weatherDescription(from code: Int) -> String {
switch code {
case 0:
return "晴れ"
case 1, 2, 3:
return "主に晴れ、一部曇り、曇り"
case 45, 48:
return "霧、着氷霧"
case 51, 53, 55:
return "霧雨: 弱い、中程度、強い"
case 56, 57:
return "着氷性の霧雨: 弱い、強い"
case 61, 63, 65:
return "雨: 小雨、中雨、大雨"
case 66, 67:
return "着氷性の雨: 弱い、強い"
case 71, 73, 75:
return "雪: 小雪、中雪、大雪"
case 77:
return "霙(みぞれ)"
case 80, 81, 82:
return "にわか雨: 弱い、中程度、激しい"
case 85, 86:
return "にわか雪: 弱い、強い"
case 95:
return "雷雨: 弱いまたは中程度"
case 96, 99:
return "雷雨: 小さい、大きいひょうを伴う"
default:
return "不明"
}
}
こちら のドキュメントの一番下にある、「WMO Weather interpretation codes (WW)」という項目で天気コードについて解説があったので、それにしたがって、妥当な日本語に変換する switch 文を実装しています。
ContentView
と Weather
を変更すると、以下のように選んだ都市の天気情報が確認できるようになるのではないでしょうか?
都市、天気ごとのImageの切り替え
次に、都市や天気によって背景画像を切り替える処理を追加してみましょう。
まずは変更した ContentView, WeatherModel を提示します。
import SwiftUI
struct ContentView: View {
@ObservedObject var weatherViewModel = WeatherViewModel()
@State private var selectedCity: City?
init() {
_selectedCity = State(initialValue: sampleCities[0])
}
var body: some View {
NavigationSplitView {
List(sampleCities, id: \.self, selection: $selectedCity) { city in
Text(city.name)
}
.onChange(of: selectedCity) {
weatherViewModel.fetchWeatherData(
latitude: String(selectedCity!.latitude),
longitude: String(selectedCity!.longitude),
timeZone: selectedCity!.timeZone
)
}
.navigationTitle("City")
} detail: {
if let selectedCity {
VStack {
Text(selectedCity.name)
.font(.system(size: 30))
if (weatherViewModel.weatherData != nil) {
Text("\(Int(weatherViewModel.weatherData!.current.temperature2M))°").font(.system(size: 60))
HStack {
VStack {
Text("最高気温")
.font(.system(size: 30))
Text("\(Int(weatherViewModel.weatherData!.daily.temperature2MMax[0]))°").font(.system(size: 50))
}
VStack {
Text("最低気温")
.font(.system(size: 30))
Text("\(Int(weatherViewModel.weatherData!.daily.temperature2MMin[0]))°").font(.system(size: 50))
}
}
.padding(10)
Text("\(weatherDescription(from: weatherViewModel.weatherData!.current.weatherCode))").font(.system(size: 40))
} else {
Text("天気情報取得 ...")
}
}
.navigationTitle("Weather")
}
}
+ .background {
+ ZStack {
+ if (weatherViewModel.weatherData != nil) {
+ Image("\(weatherDescriptionInEnglish(from: weatherViewModel.weatherData!.current.weatherCode))\(selectedCity!.name)")
+ .ignoresSafeArea()
+ }
+ Color.blue.opacity(0.8)
+ .blendMode(.softLight)
+ }
+ }
}
}
import Foundation
struct Weather: Codable {
let latitude, longitude, generationtimeMS: Double
let utcOffsetSeconds: Int
let timezone, timezoneAbbreviation: String
let elevation: Int
let currentUnits: Units
let current: Current
let hourlyUnits: Units
let hourly: Hourly
let dailyUnits: DailyUnits
let daily: Daily
enum CodingKeys: String, CodingKey {
case latitude, longitude
case generationtimeMS = "generationtime_ms"
case utcOffsetSeconds = "utc_offset_seconds"
case timezone
case timezoneAbbreviation = "timezone_abbreviation"
case elevation
case currentUnits = "current_units"
case current
case hourlyUnits = "hourly_units"
case hourly
case dailyUnits = "daily_units"
case daily
}
}
func weatherDescription(from code: Int) -> String {
switch code {
case 0:
return "晴れ"
case 1, 2, 3:
return "主に晴れ、一部曇り、曇り"
case 45, 48:
return "霧、着氷霧"
case 51, 53, 55:
return "霧雨: 弱い、中程度、強い"
case 56, 57:
return "着氷性の霧雨: 弱い、強い"
case 61, 63, 65:
return "雨: 小雨、中雨、大雨"
case 66, 67:
return "着氷性の雨: 弱い、強い"
case 71, 73, 75:
return "雪: 小雪、中雪、大雪"
case 77:
return "霙(みぞれ)"
case 80, 81, 82:
return "にわか雨: 弱い、中程度、激しい"
case 85, 86:
return "にわか雪: 弱い、強い"
case 95:
return "雷雨: 弱いまたは中程度"
case 96, 99:
return "雷雨: 小さい、大きいひょうを伴う"
default:
return "不明"
}
}
+ func weatherDescriptionInEnglish(from code: Int) -> String {
+ switch code {
+ case 0:
+ return "Sunny"
+ case 1, 2, 3:
+ return "Cloudy"
+ case 45, 48:
+ return "Fog"
+ case 51, 53, 55:
+ return "Drizzle"
+ case 56, 57:
+ return "FreezingDrizzle"
+ case 61, 63, 65:
+ return "Rain"
+ case 66, 67:
+ return "FreezingRain"
+ case 71, 73, 75:
+ return "Snow"
+ case 77:
+ return "Sleet"
+ case 80, 81, 82:
+ return "Showers"
+ case 85, 86:
+ return "SnowShowers"
+ case 95:
+ return "Thunderstorm"
+ case 96, 99:
+ return "ThunderstormWithHail"
+ default:
+ return "Unknown"
+ }
}
weatherDescriptionInEnglish
は、天気コードを入力として、英語の天気の名前を返す関数です。ContentView
の背景画像としている画像は、それぞれの都市と天気で変わります。
背景画像は基本的に英語名で登録しているため、天気コードを英語に変換する必要がありました。
以下から分かる通り、背景画像の名前は「天気 + 都市名」で保存しています。
例えば、東京の天気が曇りの時は「CloudyTokyo」で保存された画像を背景に表示させています。
Image("\(weatherDescriptionInEnglish(from: weatherViewModel.weatherData!.current.weatherCode))\(selectedCity!.name)")
天気 × 都市 の場合の数だけ画像を用意するのは少し大変でしたが、ChatGPT の DALL·E 3 を使うとイメージに近い画像を出力してくれたので、そのまま採用しました。
ContentView
と Weather
を変更して実行してみると以下のように、都市と気温によって背景画像が切り替わることがわかります。
今回のコード全文
import SwiftUI
@main
struct WeatherApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
import SwiftUI
struct ContentView: View {
@ObservedObject var weatherViewModel = WeatherViewModel()
@State private var selectedCity: City?
init() {
_selectedCity = State(initialValue: sampleCities[0])
}
var body: some View {
NavigationSplitView {
List(sampleCities, id: \.self, selection: $selectedCity) { city in
Text(city.name)
}
.onChange(of: selectedCity) {
weatherViewModel.fetchWeatherData(
latitude: String(selectedCity!.latitude),
longitude: String(selectedCity!.longitude),
timeZone: selectedCity!.timeZone
)
}
.navigationTitle("City")
} detail: {
if let selectedCity {
VStack {
Text(selectedCity.name)
.font(.system(size: 30))
if (weatherViewModel.weatherData != nil) {
Text("\(Int(weatherViewModel.weatherData!.current.temperature2M))°").font(.system(size: 60))
HStack {
VStack {
Text("最高気温")
.font(.system(size: 30))
Text("\(Int(weatherViewModel.weatherData!.daily.temperature2MMax[0]))°").font(.system(size: 50))
}
VStack {
Text("最低気温")
.font(.system(size: 30))
Text("\(Int(weatherViewModel.weatherData!.daily.temperature2MMin[0]))°").font(.system(size: 50))
}
}
.padding(10)
Text("\(weatherDescription(from: weatherViewModel.weatherData!.current.weatherCode))").font(.system(size: 40))
} else {
Text("天気情報取得 ...")
}
}
.navigationTitle("Weather")
}
}
.background {
ZStack {
if (weatherViewModel.weatherData != nil) {
Image("\(weatherDescriptionInEnglish(from: weatherViewModel.weatherData!.current.weatherCode))\(selectedCity!.name)")
.ignoresSafeArea()
}
Color.blue.opacity(0.8)
.blendMode(.softLight)
}
}
}
}
import Foundation
struct City: Codable, Identifiable, Hashable {
var id: String { name }
let name: String
let latitude: Double
let longitude: Double
let timeZone: String
enum CodingKeys: String, CodingKey {
case name, latitude, longitude
case timeZone = "time_zone"
}
}
let sampleCities = [
City(name: "Tokyo", latitude: 35.6895, longitude: 139.6917, timeZone: "Asia%2FTokyo"),
City(name: "Osaka", latitude: 34.686, longitude: 135.520, timeZone: "Asia%2FTokyo"),
City(name: "Nagoya", latitude: 35.1709, longitude: 136.8815, timeZone: "Asia%2FTokyo"),
City(name: "Hokkaido", latitude: 43.0614, longitude: 141.3538, timeZone: "Asia%2FTokyo"),
City(name: "Okinawa", latitude: 26.2123, longitude: 127.6790, timeZone: "Asia%2FTokyo"),
]
import Foundation
// MARK: - Weather
struct Weather: Codable {
let latitude, longitude, generationtimeMS: Double
let utcOffsetSeconds: Int
let timezone, timezoneAbbreviation: String
let elevation: Int
let currentUnits: Units
let current: Current
let hourlyUnits: Units
let hourly: Hourly
let dailyUnits: DailyUnits
let daily: Daily
enum CodingKeys: String, CodingKey {
case latitude, longitude
case generationtimeMS = "generationtime_ms"
case utcOffsetSeconds = "utc_offset_seconds"
case timezone
case timezoneAbbreviation = "timezone_abbreviation"
case elevation
case currentUnits = "current_units"
case current
case hourlyUnits = "hourly_units"
case hourly
case dailyUnits = "daily_units"
case daily
}
}
// MARK: - Current
struct Current: Codable {
let time: String
let interval: Int
let temperature2M: Double
let rain, weatherCode: Int
enum CodingKeys: String, CodingKey {
case time, interval
case temperature2M = "temperature_2m"
case rain
case weatherCode = "weather_code"
}
}
// MARK: - Units
struct Units: Codable {
let time: String
let interval: String?
let temperature2M, rain, weatherCode: String
enum CodingKeys: String, CodingKey {
case time, interval
case temperature2M = "temperature_2m"
case rain
case weatherCode = "weather_code"
}
}
// MARK: - Daily
struct Daily: Codable {
let time: [String]
let weatherCode: [Int]
let temperature2MMax, temperature2MMin: [Double]
enum CodingKeys: String, CodingKey {
case time
case weatherCode = "weather_code"
case temperature2MMax = "temperature_2m_max"
case temperature2MMin = "temperature_2m_min"
}
}
// MARK: - DailyUnits
struct DailyUnits: Codable {
let time, weatherCode, temperature2MMax, temperature2MMin: String
enum CodingKeys: String, CodingKey {
case time
case weatherCode = "weather_code"
case temperature2MMax = "temperature_2m_max"
case temperature2MMin = "temperature_2m_min"
}
}
// MARK: - Hourly
struct Hourly: Codable {
let time: [String]
let temperature2M: [Double]
let rain, weatherCode: [Int]
enum CodingKeys: String, CodingKey {
case time
case temperature2M = "temperature_2m"
case rain
case weatherCode = "weather_code"
}
}
func weatherDescription(from code: Int) -> String {
switch code {
case 0:
return "晴れ"
case 1, 2, 3:
return "主に晴れ、一部曇り、曇り"
case 45, 48:
return "霧、着氷霧"
case 51, 53, 55:
return "霧雨: 弱い、中程度、強い"
case 56, 57:
return "着氷性の霧雨: 弱い、強い"
case 61, 63, 65:
return "雨: 小雨、中雨、大雨"
case 66, 67:
return "着氷性の雨: 弱い、強い"
case 71, 73, 75:
return "雪: 小雪、中雪、大雪"
case 77:
return "霙(みぞれ)"
case 80, 81, 82:
return "にわか雨: 弱い、中程度、激しい"
case 85, 86:
return "にわか雪: 弱い、強い"
case 95:
return "雷雨: 弱いまたは中程度"
case 96, 99:
return "雷雨: 小さい、大きいひょうを伴う"
default:
return "不明"
}
}
func weatherDescriptionInEnglish(from code: Int) -> String {
switch code {
case 0:
return "Sunny"
case 1, 2, 3:
return "Cloudy"
case 45, 48:
return "Fog"
case 51, 53, 55:
return "Drizzle"
case 56, 57:
return "FreezingDrizzle"
case 61, 63, 65:
return "Rain"
case 66, 67:
return "FreezingRain"
case 71, 73, 75:
return "Snow"
case 77:
return "Sleet"
case 80, 81, 82:
return "Showers"
case 85, 86:
return "SnowShowers"
case 95:
return "Thunderstorm"
case 96, 99:
return "ThunderstormWithHail"
default:
return "Unknown"
}
}
import SwiftUI
import Foundation
class WeatherViewModel: ObservableObject {
@Published var weatherData: Weather?
func fetchWeatherData(latitude: String, longitude: String, timeZone: String) {
let urlString = "https://api.open-meteo.com/v1/forecast?latitude=\(latitude)&longitude=\(longitude)¤t=temperature_2m,rain,weather_code&hourly=temperature_2m,rain,weather_code&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=\(timeZone)"
guard let url = URL(string: urlString) else {
print("Invalid URL")
return
}
URLSession.shared.dataTask(with: url) { data, _, error in
DispatchQueue.main.async {
if let data = data {
do {
let decodedData = try JSONDecoder().decode(Weather.self, from: data)
self.weatherData = decodedData
} catch {
print("Error decoding: \(error)")
}
} else if let error = error {
print("Error fetching data: \(error)")
}
}
}.resume()
}
}
まとめ
最後まで読んでいただいてありがとうございました。
今回は複数の都市の天気情報を取得、表示するまでを実装しました。
次のパートでは気温変化の Chart の実装などをやっていければと思います。
間違い等あれば指摘していただければ嬉しいです。
参考
Discussion