LoginSignup
1
0

【XCode15】APIの非同期処理で得たデータをTableViewに表示させる方法

Posted at

はじめに

研修として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データのデコードのために、データ構造を作成しておく。

SearchResponse.swift
struct SearchResponse: Decodable {
    let daily: WeatherData
}
WeatherData.swift
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で呼び出すと以下の通りである。

ViewController.swift
class ViewController: UIViewController {
    
    private var weatherData: WeatherData?
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        fetchArticle(){ fetchWeatherData in
            
            // fetchWeatherDataに受け取ったデータが入っているので、ここに必要な処理を記載する
            // 例:クラス内に定義したweatherData変数に格納する
            self.weatherData = fetchWeatherData
            
        }
    }
}

こうすることで、非同期処理が実行された後に取得したデータを変数に格納できる。

TableVeiwで表示させる

上記で取得したデータ構造はリストでないため、TableViewに表示させるためにはデータ形状を修正する必要があるが、今回は主旨ではないため省略する。
形状を修正した後の構造は、以下の通りである。

Weathers.swift
struct Weathers {
    let date: String
    let weather: String
    let tempMax: String
    let tempMin: String
    let rainPer: String
}

ViewControllerクラスで、Weathers型のリストとして定義する。

ViewController.swift
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へデータを渡す際は、ライフサイクルに気をつけること
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0