【Swift】API、JSON解析を使う

今回はこちら↓で作った`Swift5Bokete`アプリの復習
[【iOS14対応】未経験者がiPhoneアプリ開発者になるための全て iOS Boot Camp](https://www.udemy.com/share/103I3AAEcTdVhUTHQH/)

![61dfbaebcff9d63d53eabc251ec044c4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956215/b63f2f97-4f1e-3fab-efe0-d2110a0488ec.gif)

検索欄にキーワードを入力すると、関連する画像が出てきます。
気に入った画像に適当にネタを入れて`決定`ボタンを押し、`シェアする`ボタンを押すとスクリーンショットが作られ、Twitter等に投稿することができます。

このアプリでは
①ライブラリの導入方法
②APIの使い方
③JSONの使い方
を学びました。

# cocoapodsでライブラリを導入する
ライブラリに関しては動画内でも[こちら]('こちら' https://qiita.com/ShinokiRyosei/items/3090290cb72434852460)のサイトを参照されていたので、このサイトを参考にするのが良いかと思います。

今回使用するライブラリは

```
pod 'SwiftyJSON'　=> JSONを使用するライブラリ
pod 'Alamofire' => ネットワークを使用するライブラリ
pod 'SDwebImage' => URLで引っ張ってきた画像を高速で使用することができるライブラリ
```
の3つです。
UIは以下の通り。
![スクリーンショット 2021-02-19 12.11.18.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956215/1e6473e9-1dad-f51f-552e-5389511e0812.jpeg)
画像上に各パーツの名前(インスタンス名)を書いています。

`ViewController`↑で導入したライブラリを使用するのと、デバイス内のアルバムを使用するために

```ViewController.swift
import Alamofire
import SwiftyJSON
import SDWebImage
import Photos
```
を`class`の前に追記します。

作成したスクリーンショットをアルバムに保存するためのプログラムを、`viewDidLoad`内に記述します。

```viewController.swift
PHPhotoLibrary.requestAuthorization { (status) in
  switch(status){
    case .authorized: break
    case .denied: break
    case .noDetermined: break
    case .restricted: break
    }
}
```

# APIについて
APIは`Application Program Interface`の略です。
簡単に言うと、とあるサービスがソフトウェアの一部を公開して、他のソフトウェアに機能を使えるようにできます(多分)
公開元が発行しているAPIキーを使い、発行元が公開している仕様書に従ってプログラムを記述することで、自分のソフトウェアに機能を導入することができます。

今回使用するのが[pixabey]('pixabay' https://pixabay.com/ja/service/about/api/)と言う無償画像提供サイトのAPI。
このAPIを使用して行いたいのは、アプリ内の`SearchTextField`に入力したキーワードで画像を`pixabay`から引っ張り出してくることです。
流れとしては、`https://pixabay.com/api/?key=[個人のAPIキー]&q=[検索ワード]`で`pixabay`のサーバーにリクエストを送ると、`json形式`でレスポンス(検索結果)が返ってくるので、このデータに`json解析`を行って画像を引っ張ってきます。

例えば検索ワードを`yellow+flower`とした場合、`pixabay`のレスポンスは以下のような形で返ってきます。
![スクリーンショット 2021-02-19 14.05.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956215/2380cf78-c67b-59e8-3214-db43df7f4047.png)

プログラムを書きます。
まず、画像のURLを取得するメソッドを作成します。

```ViewController.swift
func getImages(keyword:String) {
  //APIを使う
    let url = "https://pixabay.com/api/?key=[個人のAPIキー]&q=\(keyword)"
    //Alamofireを使ってHTTPリクエストを行う
    AF.request(url, method: get, parameters: nil, encoding: JsonEncoding.default).responseJSON{ (response) in //<= クロージャー
      switch response.result{
        case .success: //<=サーバーからリクエストを正常に受け取れたかどうかで条件分岐
    //JSON形式で返ってきたデータにJSON解析を行う
          let json:JSON = JSON(response.date as Any) //<=ここでデータを取得する
    //必要なデータを取り出し変数に格納する
          var imageString = json["hits"][self.count]["webformatURL"].string //<=hitsの配列内にあるwebformatURLをとってくる
          self.odaiImageView.sd_setImage(with: URL(string: imageString!), completed:nil)

        case .failure:
          print(error)
        }
      }
    }
```
流れをざっくり説明すると...
検索欄に入力されたキーワードが、APIキーと共にパラメーターになって`pixabay`に送信されます。
すると、上のようなデータが`JSON形式`と言う形で返ってきます。
そのデータは辞書型で値が入っています。
その中の、`hits`というキーが持っている配列の中に、検索ワードと一致する分だけ同じ数の配列が入っています。
各配列の中の`webformatURL`を、`ViewController`の`odaiImageView`に表示させたい、というわけです。

そして`次のお題`ボタンをタップすると、次の画像が表示されるようにしたいので、`nextOdai`という名前でアクションを作成します。

```ViewController.swift
@IBAction func nextOdai(_ sender: Any) {
  count += 1 //<= 冒頭でvar count = 0と宣言しておく
  if searchTextField.text == ""{
    getImage(keyword: "funny")
  } else {
    getImage(keyword: searchTextField.text!)
  }
}
```
`count`をインクリメントすることで、`次のお題`ボタンを押すたびに`JSON`内の配列を順番に参照することができます。

但し、`次へ`ボタンを押しまくって、["hits"]内の配列の数より`count`が多くなってしまうと、参照する値がなくなってしまい、エラーになってしまします。これを避けるために、`getImages`メソッド内の`let imageString =`...`.string`の行の次に、以下のプログラムを追記します。

```ViewController.swift
if imageString == nil {
  imageString = json["hits"][0]["webformatURL"].string
                    //↓これは画像を表示できるようにするために導入したSDWebImageのメソッドです
  self.odaiImageView.sd_setImage(with: URL(string: imageString!), completed: nil)
} else {
  self.odaiImageView.sd_setImage(with: URL(string: imageString!), completed: nil)
}
```
次に、🔍ボタンを押した時の機能を書きます。

```ViewController.swift
@IBAction func searchAction(_ sender: Any) {
  self.count = 0
  if searchTextField.text == "" {
    getImages(keyword: "funny")
  } else {
    getImages(keyword: searchTextField.text!)
  }
}
```
上との違いはというと`self.count = 0`ですが、`searchAction`は検索して一番最初のデータを持ってくるので、`count = 0`、すなわち`["hits"][0]`を持ってくる必要があるのでこの記述が必要です。(多分)

次に`決定`ボタンが押された時の機能を書きます。
`決定`ボタンが押された時の流れとしては、`ViewController`で選択した画像とコメントを次の画面に遷移して渡す必要があります。
あと、画面間で値を受け渡しするための`prepare for segue`も一緒に書きます。

```ViewController.swift
@IBAction func done(_sender: Any) {
  performSegue(withIdentifier: "next", sender: nil)
}
override func prepare(for segue: UIStoryBoardSegue, sender: Any?) {
  let shareVC = segue.destination as? ShareViewController
  shareVC?.resultImage = odaiImageView.image!
  shareVC?.commentString = commentTextView.text
}
  
```
遷移先のプログラムを書きます。

```ShareViewController.swift
var resultImage = UIImage()
var commentsString = String()
var screenShotImage = UIImage()
```
上の2つの値は`ViewController`ですでに値を受け取っているので、`viewDidLoad`メソッド内で

```ShareViewController.swift
resultImageView.image = resultImage
commentLabel.text = commentString
```
と記述して遷移先の画面に値を反映します。

次にスクリーンショットの機能を書きます。
これは何も考えず、こういう風に書くんだな、と思っておくのが良さそうです。

```ShareViewController.swift
func takeScreenshot(){
 let width = CGFloat(UIScreen.main.bounds.size.width)
 let height = CGFloat(UIScreen.main.bounds.size.height/1.3)
 let size = CGSize(width: width, height: height)
        
 UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        
 self.view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
 screenShotImage = UIGraphicsGetImageFromCurrentImageContext()!
 UIGraphicsEndImageContext()
}
```
そして、`共有`ボタンを押したらスクリーンショットが発動して共有できるようにしたいので、↑のメソッドを利用して、

```ShareViewController.swift
@IBAction func share(_ sender: Any) {
  //スクリーンショットを撮る
  takeScreenshot()
        
  let items = [screenShotImage] as [Any]
 //アクティビティビューに乗っけてシェアする
  let activityView = UIActivityViewController(activityItems: items, applicationActivities: nil)
  present(activityView, animated: true, completion: nil)
}
```
これで一通り完成です。

# 感想
`Ruby on Rails`でもいくつかライブラリ使用しましたが、`Swift`のもあるんですね(あほ)。
`!`だったり`?`だったり`self`書いたりって細かい違いのところがまだ把握できてないので、今後の学習の中でつかんでいきたいと思います。