Google画像検索APIをiOSで利用する

**完成品**
<img width="372" alt="スクリーンショット 2017-02-20 10.38.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/137840/c010a0d7-e57b-22be-6091-894284c5ef8a.png">
Searchボタンで画像を表示！
左右にSwipeで画像を切り替える！


※この記事では、Xcode8.2、Swift3を使用しています。


***
まず、適当な名前をつけてSingle View Applicationのプロジェクトを作成します。

<img width="1437" alt="スクリーンショット 2017-02-19 9.33.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/137840/f0ffbf8d-380a-1de4-5cd8-58a6f5ebf2f5.png">


ImageView、TextField、Buttonを適当に配置。
Buttonをダブルクリックダブルクリックして名前をSearchとかに変えておきます。
<img width="1440" alt="スクリーンショット 2017-02-19 9.35.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/137840/4b09dd26-8a25-a55e-a839-43af1bcdb5a8.png">

次に、Google画像検索をするために必要な
・APIキー
・検索エンジンキー
を取得する必要があります。

まずAPIはこちらから取得します。
https://console.developers.google.com/?hl=JA


プロジェクトを作成し、認証情報を作成して、APIを有効化するという手順のようです。
認証情報→プロジェクト作成→認証情報作成で、APIキーが作成されます。
<img width="963" alt="スクリーンショット 2017-02-19 9.50.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/137840/935f4aea-a5dd-4a86-53cb-41bf88cf1ff7.png">
キーの制限は無くても大丈夫みたいです。



次に検索エンジンキーを取得します。
https://cse.google.com/cse/
にて、Google Custom Search Engineを作成します。
Addをクリックして作成してください。
設定はこんな感じで良いと思います。画像検索をオンにするのを忘れずに。
詳細→検索エンジンIDをクリックで検索エンジンIDを取得できます。
<img width="635" alt="スクリーンショット 2017-02-19 16.29.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/137840/faec606a-d686-14dd-942d-9fd40a21fbfc.png">


* * *
さて、必要なものは揃ったのでアプリを作っていきましょう。
まずは先程配置したImageViewとTextFieldを、Storyboardからコントロール+ドラッグ＆ドロップでコードのほうに引っ張って、適当な名前をつけてOutlet接続します。
ButtonのほうはAction接続をし、タップされた時の処理を{}内に書いていけるようにします。

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {
   @IBOutlet weak var image: UIImageView!
   @IBOutlet weak var imageTextField: UITextField!
   @IBAction func searchButtonTapped(_ sender: Any) {
   }
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

```

ここで、画像をスワイプで切り替えれるようにするための下準備をします。

Swipe Gesture Recognizerを2つ、ImageViewの上にドラッグ＆ドロップし、Attribute Inspectorで片方をRight、もう片方をLeftに設定し、コード部にCtrl+ドラッグ＆ドロップで適当な名前をつけてAction接続します。

<img width="646" alt="スクリーンショット 2017-02-19 21.23.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/137840/b47d596a-d720-0c8a-00f4-01c031fe3dc1.png">

ImageViewは、デフォルトではタッチが効かないようになっているため、Attribute Inspector→View→Interactionの、User Interaction Enabledにチェックを入れましょう。
<img width="260" alt="スクリーンショット 2017-02-19 21.42.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/137840/4050d3c0-bcda-0840-56a8-993e00615d2f.png">

また、画像がImageViewの正方形の中に、縦横比はそのままで、大きく表示されるように、Content ModeをAspect Fitに設定しておきます。
<img width="258" alt="スクリーンショット 2017-02-20 11.27.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/137840/daa5fa91-cafa-396c-2675-1687a6242607.png">


準備はできたので、残りのプログラムを書きます

```swift:ViewController.swift

import UIKit

class ViewController: UIViewController,UITextFieldDelegate{
    
    @IBAction func rightSwiped(_ sender: Any) {
        if(imageSub != 0){
            imageSub = imageSub-1
        }
        if let url = URL(string: wordImageArray[imageSub]) {
            let req = URLRequest(url: url)
            let task = URLSession.shared.dataTask(with: req, completionHandler: {data, response, error in
                if let data = data {
                    if let anImage = UIImage(data: data) {
                        DispatchQueue.main.async {
                            self.image.image = anImage
                        }
                    }
                }
            })
            task.resume()
        }
    }
    @IBAction func leftSwiped(_ sender: Any) {
        //imageSubを1つすすめる
        imageSub = imageSub+1
        
        //画像配列の末尾に到達している場合、配列に新たな10件を登録する
        if((imageSub) == wordImageArray.count){
            
            //パラメータのstartを決める
            let startPara: String = String(imageSub)
            
            //検索ワード
            let pasteboard = UIPasteboard.general
            let copiedText = pasteboard.string
            
            // パラメータを指定する
            let parameter = ["key": apikey,"cx":cx,"searchType":searchType,"q":copiedText,"start":startPara]
            
            // パラメータをエンコードしたURLを作成する
            let requestUrl = createRequestUrl(parameter: parameter as! [String : String])
            
            // APIをリクエストする
            request(requestUrl: requestUrl) { result in
                if let url = URL(string: self.wordImageArray[self.imageSub+1]) {
                    let req = URLRequest(url: url)
                    let task = URLSession.shared.dataTask(with: req, completionHandler: {data, response, error in
                        if let data = data {
                            if let anImage = UIImage(data: data) {
                                DispatchQueue.main.async {
                                    self.image.image = anImage
                                }
                            }
                        }
                    })
                    task.resume()
                }
            }
        }else{
            if let url = URL(string: wordImageArray[imageSub]) {
                let req = URLRequest(url: url)
                let task = URLSession.shared.dataTask(with: req, completionHandler: {data, response, error in
                    if let data = data {
                        if let anImage = UIImage(data: data) {
                            DispatchQueue.main.async {
                                self.image.image = anImage
                            }
                        }
                    }
                })
                task.resume()
            }
        }
    }
    @IBOutlet weak var image: UIImageView!
    @IBOutlet weak var imageTextField: UITextField!
    
    // APIを利用するためのアプリケーションID
    let apikey: String = "*********************"
    
    //APIを利用するためのサーチエンジンキー
    let cx: String = "*********************"
    
    //利用するAPIのサーチタイプ
    let searchType: String = "image"
    
    // APIのURL
    let entryUrl: String = "https://www.googleapis.com/customsearch/v1"
    
    //関連画像URLを格納する配列
    var wordImageArray: [String] = [String]()
    
    //現在表示している画像の添字を格納する変数
    var imageSub :Int = 0;
    
    
    @IBAction func searchButtonTapped(_ sender: Any) {
        let query = imageTextField.text
        //配列の要素全削除
        wordImageArray.removeAll()
        // パラメータを指定する
        let parameter = ["key": apikey,"cx":cx,"searchType":searchType,"q":query]
        
        // パラメータをエンコードしたURLを作成する
        let requestUrl = createRequestUrl(parameter: parameter as! [String : String])
        
        // APIをリクエストする
        request(requestUrl: requestUrl) { result in
            if let url = URL(string: self.wordImageArray[0]) {
                let req = URLRequest(url: url)
                let task = URLSession.shared.dataTask(with: req, completionHandler: {data, response, error in
                    if let data = data {
                        if let anImage = UIImage(data: data) {
                            DispatchQueue.main.async {
                                self.image.image = anImage
                            }
                        }
                    }
                })
                task.resume()
            }
        }
    }
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // textFiel の情報を受け取るための delegate を設定
        imageTextField.delegate = self
        
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool{
        // キーボードを閉じる
        textField.resignFirstResponder()
        
        return true
    }
    
    // パラメータのURLエンコード処理
    func encodeParameter(key: String, value: String) -> String? {
        // 値をエンコードする
        guard let escapedValue = value.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) else {
            // エンコード失敗
            return nil
        }
        // エンコードした値をkey=valueの形式で返却する
        return "\(key)=\(escapedValue)"
    }
    
    // URL作成処理
    func createRequestUrl(parameter: [String: String]) -> String {
        var parameterString = ""
        for key in parameter.keys {
            // 値の取り出し
            guard let value = parameter[key] else {
                // 値なし。次のfor文の処理を行なう
                continue
            }
            
            // 既にパラメータが設定されていた場合
            if parameterString.lengthOfBytes(using: String.Encoding.utf8) > 0 {
                // パラメータ同士のセパレータである&を追加する
                parameterString += "&"
            }
            
            
            
            // 値をエンコードする
            guard let encodeValue = encodeParameter(key: key, value: value) else {
                // エンコード失敗。次のfor文の処理を行なう
                continue
            }
            // エンコードした値をパラメータとして追加する
            parameterString += encodeValue
            
        }
        let requestUrl = entryUrl + "?" + parameterString
        return requestUrl
    }
    
    // 検索結果をパース
    func parseData(items: [Any], resultHandler: @escaping (([String]?) -> Void)) {
        
        for item in items {
            
            // レスポンスデータから画像の情報を取得する
            guard let item = item as? [String: Any], let imageURL = item["link"] as? String else {
                resultHandler(nil)
                return
            }
            print(imageURL)
            
            // 配列に追加
            wordImageArray.append(imageURL)
        }
        
        resultHandler(wordImageArray)
    }
    
    // リクエストを行なう
    func request(requestUrl: String, resultHandler: @escaping (([String]?) -> Void)) {
        // URL生成
        guard let url = URL(string: requestUrl) else {
            // URL生成失敗
            resultHandler(nil)
            return
        }
        
        // リクエスト生成
        let request = URLRequest(url: url)
        
        // APIをコールして検索を行う
        let session = URLSession.shared
        let task = session.dataTask(with: request) { (data:Data?, response:URLResponse?, error:Error?) in
            // 通信完了後の処理
            print(NSString(data: data!, encoding: String.Encoding.utf8.rawValue) ?? "")
            
            // エラーチェック
            guard error == nil else {
                // エラー表示
                let alert = UIAlertController(title: "エラー", message: error?.localizedDescription, preferredStyle: UIAlertControllerStyle.alert)
                
                // UIに関する処理はメインスレッド上で行なう
                DispatchQueue.main.async {
                    self.present(alert, animated: true, completion: nil)
                }
                resultHandler(nil)
                return
            }
            
            // JSONで返却されたデータをパースして格納する
            guard let data = data else {
                // データなし
                resultHandler(nil)
                return
            }
            
            // JSON形式への変換処理
            guard let jsonData = try! JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: Any] else {
                // 変換失敗
                resultHandler(nil)
                return
            }
            
            // データを解析
            guard let resultSet = jsonData["items"] as? [Any] else {
                // データなし
                resultHandler(nil)
                return
            }
            self.parseData(items: resultSet, resultHandler: resultHandler)
        }
        // 通信開始
        task.resume()
    }
}
```


最後に、http通信を許可しないと一部の画像が表示されませんので、ATSを無効にします。
Info.plistにApp Transport Security Settingの項目を追加し、Allow Arbitrary LoadsをYESに。
<img width="918" alt="スクリーンショット 2017-02-20 10.32.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/137840/b385627d-b471-5b60-4cb3-4dada44a931a.png">



完成
<img width="372" alt="スクリーンショット 2017-02-20 10.22.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/137840/58f0c68a-170d-dbfd-c513-f5693796d818.png">


Google Custom APIの無料版は、1日100クエリまでの制限があるみたいです！！！