はじめに
研修としてAPIを使った天気予報アプリの作成を行った際、APIの非同期処理で得たデータをTableVeiwに表示させる方法がわからず苦戦したため、理解したことを記載する。
APIの非同期通信
APIとデータ構造について
天気予報のAPIはOpen-Meteoを使用。
今回使うURLは以下の通りである。
https://api.open-meteo.com/v1/forecast?latitude=35.6894&longitude=139.6917&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max
APIで受け取ったJSONデータのデコードのために、データ構造を作成しておく。
struct SearchResponse: Decodable {
let daily: WeatherData
}
struct WeatherData: Decodable {
let date: [String]
let weatherCode: [Int]
let tempMax: [Float]
let tempMin: [Float]
let rainPer: [Int]
enum CodingKeys: String, CodingKey {
case date = "time"
case weatherCode = "weather_code"
case tempMax = "temperature_2m_max"
case tempMin = "temperature_2m_min"
case rainPer = "precipitation_probability_max"
}
}
SearchResponseには受け取ったデータのkeyがdailyのデータのみが入り、その構造がWeatherDataとなる。
非同期通信について
非同期通信にはクロージャを用いる。
// APIから天気予報を取得
private func fetchArticle(completion: @escaping (WeatherData) -> Void) {
let url = "https://api.open-meteo.com/v1/forecast"
guard var urlComponents = URLComponents(string: url) else { return }
let queryItems = ["latitude": "35.6894",
"longitude": "139.6917",
"daily": "weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max"].map {
URLQueryItem(name: $0.key, value: $0.value)
}
urlComponents.queryItems = queryItems
URLSession.shared.dataTask(with: urlComponents.url!, completionHandler: { data, response, error in
do {
guard let data = data else { return }
let searchResponse = try JSONDecoder().decode(SearchResponse.self, from: data)
DispatchQueue.main.async() {
completion(searchResponse.daily)
}
} catch {
print(error.localizedDescription)
}
}).resume()
}
completion(searchResponse.daily)
により、WeatherData型のデータが変数として返る関数の様な動きをする。
この関数をViewControllerで呼び出すと以下の通りである。
class ViewController: UIViewController {
private var weatherData: WeatherData?
override func viewDidLoad() {
super.viewDidLoad()
fetchArticle(){ fetchWeatherData in
// fetchWeatherDataに受け取ったデータが入っているので、ここに必要な処理を記載する
// 例:クラス内に定義したweatherData変数に格納する
self.weatherData = fetchWeatherData
}
}
}
こうすることで、非同期処理が実行された後に取得したデータを変数に格納できる。
TableVeiwで表示させる
上記で取得したデータ構造はリストでないため、TableViewに表示させるためにはデータ形状を修正する必要があるが、今回は主旨ではないため省略する。
形状を修正した後の構造は、以下の通りである。
struct Weathers {
let date: String
let weather: String
let tempMax: String
let tempMin: String
let rainPer: String
}
ViewControllerクラスで、Weathers型のリストとして定義する。
class ViewController: UIViewController {
private var weathers = [Weathers]()
...
}
定義したweathersをデータとして使うように、TableViewのcellの設定を行うと以下のようになる。
extension ViewController:UITableViewDataSource, UITableViewDelegate {
// cell数の設定
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return weathers.count
}
// cellの中身
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell
let weather = weathers[indexPath.row]
cell.dateLabel.text = weather.date
cell.weatherLabel.text = weather.weather
cell.rainPerLabel.text = weather.rainPer
cell.tempMaxLabel.text = weather.tempMax
cell.tempMinLabel.text = weather.tempMin
return cell
}
}
しかし、このままでは表示できない。
これにはUIViewControllerのライフサイクルが影響している。
ライフサイクルとは、簡単にいうと、処理の実行順のことである。
今回の場合は、非同期処理で天気予報データを取得し終わる前に、TableViewの表示処理が終わってしまうため、
データがない状態でTableViewの表示処理が行われている。
それを解決するために、変数の定義を以下の通りに変更する。
private var weathers = [Weathers](){
didSet {
tableView.reloadData() // tableViewを更新
}
}
変数にdidSetを定義することで、変数の値が変更された後に何かしらの処理を行うことができる。
そこにtableView.reloadData()を記載することで、TableViewの表示の更新を行うことができる。
こうすることで、データ取得の非同期処理終了後にTableViewの表示処理を再度実行させることができ、TableViewにデータが表示される。
まとめ
今回はクロージャを用いた非同期処理でAPIのデータを受け取る方法と、受け取った後のデータをTableVeiwに表示させる方法についてまとめた。
ポイント
- クロージャを用いた非同期処理で、処理が終わった後にデータを変数に格納すること
- TableViewへデータを渡す際は、ライフサイクルに気をつけること