お天気APIを用いて傘が必要か判断するアプリ作成　備忘録

はじめに
-
初心者が学習とポートフォリオをかねて作成しました。
作成し始めて1〜2ヶ月程度ですので、リファクタリングやコーディング等のアドバイスあればいただきたいです。


アプリ概要
-

サラリーマンなら今日傘が必要かを一瞬で確認できるアプリがあれば便利だなと思い作成。

ボタンを押す
↓
GPSから今いる場所の緯度・経度を割り出す
↓
緯度・経度から逆ジオコーディング
↓
今いる都道府県を割り出す
↓
お天気APIから今いる都道府県の18時間先の降水確率を取得
↓
取得した降水確率の最大値をピックアップ
↓
傘が必要かそうでないかを表示

すでにそのようなアプリがリリースされていましたが、僕が望んでいたアプリとは違いましたので、ポートフォリオ作成もかねて作成してみました。

＜一連の流れgif＞
![テキスト追加動画.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803997/7c08d8a0-c177-2dfa-8ecb-ddc694a46233.gif)



<dl>

動作環境
-
| 対象 | バージョン |
|:-:|:-:|
|iOS | 14.0 |
| macOS  | Catalina 10.15.7  |
| Xcode  | 12.0  |
| Swift  | 5.3 |

使用したお天気API
[OpenWeatherMap](https://openweathermap.org/)


Podをインストールする
-
今回は
"Alaomofire”と”SwiftyJSON”をインストールしました。

```swift:Podfile
pod "Alamofire"
pod "SwiftyJSON"
```

[参考サイト](https://qiita.com/ume1126/items/9ec378c02ca1b06287e9)


Main.storyboadを作成する
-

Main.storyboad
![スクリーンショット 2020-10-20 21.01.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803997/51b6228c-6de1-866b-3b84-bcd79daa6971.png)


今回はHomeVCとUmbrellaVCを作成しました。
アプリ起動後にHomeVCへ移行→「Location Information」ボタンタップ→UmbrellaVCへ移行
①labelには、逆ジオコーディングで取得した現在地の都道府県を呼び出し
②labelには、お天気APIから現在地の降水確率を呼び出し
③labelには、降水確率から傘が必要か不必要か判断


実装する
-
まずはお天気APIで取得するデータのmodelを作成しておく。

<details><summary>CityModel</summary><div>

```CityModel.swift
import Foundation

struct cityModel:Decodable{
    
    var list: [List]

    struct List:Decodable {
        var pop:Double
    }
    
}
```

</div></details>

ここに記載する構造体は取得するAPIデータによって異なると思います。
今回使用したお天気APIの「OpenWeather」では、上記のような取得をしました。

<details><summary>OpenWeatherで取得したJSONデータ</summary><div>

この中の”pop”という項目の数値が今回取得しようとしている数値です（Double型）
![スクリーンショット 2020-10-20 21.14.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/803997/5f87fcf4-ba47-bc80-9132-98cfe0450e04.png)

</div></details>

[参考サイト](https://qiita.com/uhooi/items/c0e083f916dc516175bd)


----

各labelに変数を代入しておく。

<details><summary>UmbrellaVC</summary><div>

```UmbrellaVC.swift

import UIKit
import CoreLocation

class UmbrellaVC: UIViewController {
    
    @IBOutlet weak var label1: UILabel!
    var locationText:String = "大阪府"
    
    @IBOutlet weak var label2: UILabel!
    var popText:Int = 0
    
    @IBOutlet weak var label3: UILabel!
    var umbrellaJudgement:String = "傘が必要"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        label1.text = locationText
        label2.text = "\(popText)" + "%"
        label3.text = umbrellaJudgement
        
        self.label3.layer.borderWidth = 2.0
        self.label3.layer.borderColor = UIColor.black.cgColor
        self.label3.layer.cornerRadius = 20

        //降水確率によって出てくる文字の色を変えて一目でわかるようにする
        if popText >= 30 && popText < 70{
            label3.textColor = .orange
            
        }else if popText >= 70{
            label3.textColor = .red
        }
    }
}

```

</div></details>

----

UmbrellaVCで設定した変数に代入する。

<details><summary>HomeVC</summary><div>

```HomeVC.swift
import UIKit
import CoreLocation
import Alamofire
import SwiftyJSON

class HomeVC: UIViewController,CLLocationManagerDelegate {
    
    //各変数へ初期値を入れておく
    var latitudeNow: Double = 39.0000
    var longitudeNow: Double = 140.0000
    var locationManager: CLLocationManager!
    var administrativeArea:String = ""
    var locationNow: String = ""
    private var citymodel: cityModel?
    var doubleOfMaximumPop:Double = 100.0
    var maxPop:Int = 30
    var Judge:String = ""

 override func viewDidLoad() {
        super.viewDidLoad()

        //image viewのレイアウト設定
        umbrellaImage.image = UIImage(named:"umbrellaImage")
        umbrellaImage.layer.cornerRadius = 10
                
        //locationManagerをviewDidload時に呼び出す（位置情報を更新する）
        locationManagerDidChangeAuthorization(CLLocationManager())
        
        //天気取得関数の呼び出し
        getWeatherData()

    }

    //    ボタンを押した際に位置情報を取得する
    @IBAction func buttonTapped(_ sender: Any) {
        
        //ボタンを押すとlocationManagerの更新をやめる
        stopLocationManager()
        
   }

    //    ボタンを押すとsegueに移行し、UmbrellaVC内のLabel1に都道府県を記載する
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if(segue.identifier == "toUmbrellaVC") {
            let  locationNow: UmbrellaVC = (segue.destination as? UmbrellaVC)!
            locationNow.locationText = administrativeArea
            
            let popNow:UmbrellaVC = (segue.destination as? UmbrellaVC)!
            popNow.popText = Int(maxPop)
            
            let umbJud:UmbrellaVC = (segue.destination as? UmbrellaVC)!
            umbJud.umbrellaJudgement = Judge
            
        }
    }

    //ロケーションマネージャー＠iOS 14　位置情報の更新
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        locationManager = CLLocationManager()
        
        let status = manager.authorizationStatus
        
        switch status {
        case .authorizedAlways, .authorizedWhenInUse:
            locationManager.delegate = self
            locationManager.startUpdatingLocation()
            
        case .notDetermined, .denied, .restricted:
            showAlert()
            
        default:print("未処理")
        }
    }

    //locationManagerの情報更新をやめる
        func stopLocationManager(){
            locationManager.stopUpdatingLocation()
        }

    //アラートを表示する関数
    func showAlert(){
        let alertTitle = "位置情報取得が許可されていません。"
        let alertMessage = "設定アプリの「プライバシー > 位置情報サービス」から変更してください。"
        let alert: UIAlertController = UIAlertController(
            title: alertTitle, message: alertMessage, preferredStyle: UIAlertController.Style.alert
        )
        //OKボタン
        let defaultAction: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil)
        
        //UIAlertControllerにActionを追加
        alert.addAction(defaultAction)
        present(alert, animated: true, completion: nil)
        
    }

    //位置情報が更新された際、位置情報を格納する関数
    //位置情報が更新されないとlocation managerは起動しない※重要
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        
        
        let location = locations.first
        let latitude = location!.coordinate.latitude
        let longitude = location!.coordinate.longitude
        //位置情報を格納する
        self.latitudeNow = Double(latitude)
        self.longitudeNow = Double(longitude)
        
        //位置情報を取得後、逆ジオコーディングし、都道府県を割り出す
        let locationA = CLLocation(latitude: latitudeNow, longitude: longitudeNow)
        
        let geocoder: CLGeocoder = CLGeocoder()
        geocoder.reverseGeocodeLocation(locationA) { [self] (placemarks, error) in
            if let placemark = placemarks?.first {
                self.administrativeArea = placemark.administrativeArea!

            } else {
                self.administrativeArea = "bb"
            }
        }
    }

    //天気予報APIを用いて18時間後までの最大降水確率を取得する
    //OpenWeatherMapでは1日の降水確率取得が有料であり、３時間ごとの降水確率取得は無料のためそちらを使用(朝、出勤前にアプリを使うことを想定したため、問題ないと判断)
    private func getWeatherData()  {

        let id = "API ID を入力"
        let baseUrl = "http://api.openweathermap.org/data/2.5/forecast?lat=" + "\(latitudeNow)" + "&lon=" + "\(longitudeNow)" + "&exclude=daily&lang=ja&cnt=6&.pop&appid=" + "\(id)"
        
        AF.request(baseUrl, method: .get).responseJSON { [self] response in
            guard let data = response.data else {
                return
            }
            do {
                let citymodel = try JSONDecoder().decode(cityModel.self, from: data)
                
                //APIのデータをリスト表示する
                let popNumber = citymodel.list.map{ $0.pop }
            
                //リスト内のmaxデータを取得する
                var doubleOfMaximumPop = popNumber.max()
                
                //maxデータのパーセンテージ表示に変換する
                let maxPop = doubleOfMaximumPop! * 100
                
                //データがあるかどうかを判断する
                if doubleOfMaximumPop == nil{
                    print(Error.self)
                }else {
                    //データがあれば、
                    if doubleOfMaximumPop != nil{
                        //maxデータを取得する
                        doubleOfMaximumPop = self.doubleOfMaximumPop
                    }else {
                        //同じ数字であれば、その中のひとつをピックアップする
                        doubleOfMaximumPop = popNumber[0]
                    }
                }

                //maxPopへgetweather関数で取得した数値を変数へ代入する
                self.maxPop = Int(maxPop)
                
                //maxPopによって傘が必要かの判断をし、判断した文をJudgeへ代入する。
                if self.maxPop <= 30  {
                    self.Judge = "⛅️傘は不要です⛅️"
                }else if self.maxPop >= 70 {
                    self.Judge = "☔️傘が必要です☔️"
                }else {
                    self.Judge = "☂️折り畳み傘を持っていれば安心☂️"
                }
                
            }catch let error {
                print("Error:\(error)")
            }
        }
    }
}

```
</div></details>


苦労した点
-
・LocationManagerで位置情報を更新→逆ジオコーディングしデータを取得するという流れを理解していなかったため、データは取得できているが、変数に代入されないなどの不具合が発生した。


参考サイト一覧
-


[iOS14でのCore Location変更点](https://qiita.com/kj_trsm/items/66e07c61ce4a3142832f)
[関数内の変数を関数外の変数へ代入する](https://qiita.com/uhooi/items/c0e083f916dc516175bd)
[Codaleについて備忘録]
(https://qiita.com/s_emoto/items/deda5abcb0adc2217e86)


<dl>
<dt>インプット<dt>
---
<dd>**読書**<dd>
・絶対に挫折しないiPhoneアプリ開発「超」入門編
・たった2日でマスターできるiPhoneアプリ開発集中講座  



<dd>**Udemy**<dd>
・iOS12:Learn to Code & Build Real iOS 12 Apps in Swift 4.2
 概念などをイメージ図などで教えてくれたので、理解しやすかった。

・【iOS14対応】未経験者がiPhoneアプリ開発者になるための全て iOS Boot Camp

iOSは随時更新されていくので、新しい知識を常にキャッチアップしなければならないとおもった。

<dl>

<dl>
<dt>**アウトプット**<dt>
---
**Qiita**

多くの方がQiitaにソースコードを載せてくれてたおかげでここまで作成することができたため、
同じような境遇の人たちの役に立てばと思い作成した。

<dl>