Swiftでapiを使って天気予報を取得しよう

##はじめに

初心者が学習を兼ねて、作ってみました。
暖かい目でお願いします。
変なところがあったら教えてください！

##環境

Swift  5.1.2 
Xcode  11.2


使用したお天気api
http://weather.livedoor.com/weather_hacks/webservice

##Podをインストールする

```:Podfile

  pod "Alamofire" 
  pod "SwiftyJSON"

```
この2つのライブラリをインストールする事で、
簡単にHTTP通信と、JSONを扱うことができます。


##Main.storyboardを作る

<img width="226" alt="main.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398796/dc2bb4a9-4152-3fe6-c715-403939949cd0.png"><img width="222" alt="result.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398796/51c0be17-1d4e-beb2-656a-73f39763e3ce.png">


こんな感じでパーツを配置します。
今回は**pickerView**を使用して、地域を選択できるようにします。
あとはラベルとボタンを、いい感じに配置します。

`ボタンを押すと、選択した地域の天気予報を取得し、次の画面でラベルに反映するようにします。`
とてもシンプルです。

また、今回は**Navigation Controller**を使用しています。
タブの`Editor -> Embed In -> Navigation Controller`で使用できます。


##実装する

まずは、pickerViewに入れるデータをmodelに書いておきます。


```swift:CityModel.swift

import Foundation

class CityModel {

    //地域の名前とURL追加するプロパティを持ちます。   
    let code: String
    let name: String
    
    init(cityCode:String, cityName:String) {
        code = cityCode
        name = cityName
    }
}
```

```swift:CityList.swift

import Foundation

class CityList {
    
    var list = [CityModel]()
    
    //Webサイトを見て適当に地域を追加します。
    init() {
        list.append(CityModel(cityCode: "016010", cityName: "札幌"))
        list.append(CityModel(cityCode: "015010", cityName: "室蘭"))
        list.append(CityModel(cityCode: "040010", cityName: "仙台"))
        list.append(CityModel(cityCode: "110010", cityName: "さいたま"))
        list.append(CityModel(cityCode: "130010", cityName: "東京"))
        list.append(CityModel(cityCode: "140010", cityName: "横浜"))
    }
}
```


次に、最初の画面の処理を書いていきます。
この画面では、選択したデータを次の画面に渡すだけです。


```swift:ViewController.swift

import UIKit

//pickerを使うので、DelegateとDataSourceを追加します。
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
     
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var pickerView: UIPickerView!
    @IBOutlet weak var resultButton: UIButton!
    
    //次のVCに渡す変数に初期値を入れておきます。
    var cityData:[String] = ["016010", "札幌"]

    //modelのデータを持ってきます。
    let cityList = CityList()    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //pickerを使うので、デリゲートを設定します。
        pickerView.delegate = self
        pickerView.dataSource = self        
    }

    //pickerの列の数を決めます。
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
           return 1
       }
     
    //pickerの行数を決めます。
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return cityList.list.count
    }
    
    //pickerに表示するデータを決めます。
    //ここでは取得したmodelのデータのnameを表示します。
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return cityList.list[row].name
    }
    
    //選択時の挙動を決めます。
    //次の画面に渡すために、取得したmodelのデータから、codeとnameを変数に入れます。
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        cityData = [cityList.list[row].code,cityList.list[row].name]
    }
    
    //ボタン押した時の処理を書きます。画面遷移です。
    @IBAction func resultButton(_ sender: Any) {
        performSegue(withIdentifier: "result", sender: nil)
    }
    
    //次の画面にデータを渡す処理を書きます。
    //prepareは、segueが動作するとViewControllerに通知してくれます。
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "result" {
            //resultViewController(次の画面)で作った変数に、pickerで選択した地域の情報を入れます。
            let resultVC = segue.destination as! ResultViewController
            resultVC.cityData = cityData
        }
    }   
}

```


次の画面を実装します。
この画面では、天気予報の取得とラベルに反映を行います。

```swift:resultViewContoller.swift

import UIKit

//Podからインスールしたライブラリを使うのでインポートします。
import Alamofire
import SwiftyJSON

//componentsを使用するのでインポートします。
import Foundation

class ResultViewController: UIViewController {
        
    @IBOutlet weak var cityNameLabel: UILabel!
    @IBOutlet weak var detailsTextView: UITextView!
    
    @IBOutlet weak var todayDateLabel: UILabel!
    @IBOutlet weak var todayWeatherLabel: UILabel!
    @IBOutlet weak var todayHighLabel: UILabel!
    @IBOutlet weak var todayLowLabel: UILabel!
    
    @IBOutlet weak var tomorrowDateLabel: UILabel!
    @IBOutlet weak var tomorrowWeatherLabel: UILabel!
    @IBOutlet weak var tomorrowHighLabel: UILabel!
    @IBOutlet weak var tomorrowLowLabel: UILabel!
    
    let highString = "最高気温："
    let lowString = "最低気温："
    
    //取得した天気予報のデータを入れます。
    var date:String = ""
    var weather:String = ""
    var high:String = ""
    var low:String = ""
    var details:[String] = []

    //ViewController(前の画面)に渡し、選択した地域のデータを入れます。
    var cityData:[String] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        getWeatherData(row: 0)
        getWeatherData(row: 1)
    }
    
    //天気情報を取得する。
    private func getWeatherData(row: Int) {

        //前の画面から渡されたデータのcodeを、urlに組み込みます。
        let url = "http://weather.livedoor.com/forecast/webservice/json/v1?city=\(cityData[0])"
        
        //Alamofireで通信します。データを取得します。
        AF.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON {(response) in

            switch response.result {
            case .success:
     
                //jsonを取得します。
                let json:JSON = JSON(response.data as Any)

                //取得したjsonから、必要なデータを取り出します。
                let date = json["forecasts"][row]["date"].string
                let weather = json["forecasts"][row]["telop"].string
                let high = json["forecasts"][row]["temperature"]["max"]["celsius"].string
                let low = json["forecasts"][row]["temperature"]["min"]["celsius"].string
                let details = json["description"]["text"].string
                                
                //取り出したデータを、それぞれの変数に入れます。
                //データがない場合もあるので、その時は"No Data"と入れておきます。
                if date != nil {
                    self.date = String(date!.suffix(5))  //2025-04-10 -> 04-10 
                } else {
                    self.date = "No Data"
                }
                
                if weather != nil {
                    self.weather = weather!
                } else {
                    self.weather = "No Data"
                }
                
                if high != nil {
                    self.high = "\(high!)°"
                } else {
                    self.high = "No Data"
                }
                
                if low != nil {
                    self.low = "\(low!)°"
                } else {
                    self.low = "No Data"
                }
                
                if details != nil {
                    self.details = (details?.components(separatedBy: .newlines))!  //改行で分割します。
                } else {
                    self.details = ["No Details"]
                }
                
                //ラベルに反映させます。
                self.setWeatherData(row: row)
                
            case .failure(let error):
                print("-------- エラー ------")
                print(error)
            }
        }
    }

    //ラベルに反映させる処理です。
    //引数を与えて、todayとtomorrowで分岐させます。
    private func setWeatherData(row: Int) {
    
        if row == 0 {
        
            todayWeatherLabel.text = weather
            todayDateLabel.text = date
            todayHighLabel.text = highString + high
            todayLowLabel.text = lowString + low

            //分割時に配列になっているので、1つ目の要素だけ表示します。
            detailsTextView.text = details[0]
            
        } else if row == 1 {
            
            tomorrowWeatherLabel.text = weather
            tomorrowDateLabel.text = date
            tomorrowHighLabel.text = highString + high
            tomorrowLowLabel.text = lowString + low
        }
    }
}
```


##完成
<img width="384" alt="main2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398796/b34a83f5-3a40-4722-f6e9-e301e2267223.png"><img width="380" alt="result2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398796/d8c8ff18-47a6-269d-72a6-2cc529111cf4.png">



コードの内容はさておき、天気予報のデータの取得に成功しました。

ViewDidLoad内で**フォントサイズ**や**ボタンの枠線**を変更していますが、
長くなるので割愛しました。お好みでどうぞ。