完成品
Searchボタンで画像を表示!
左右にSwipeで画像を切り替える!
※この記事では、Xcode8.2、Swift3を使用しています。
まず、適当な名前をつけてSingle View Applicationのプロジェクトを作成します。
ImageView、TextField、Buttonを適当に配置。
Buttonをダブルクリックダブルクリックして名前をSearchとかに変えておきます。
次に、Google画像検索をするために必要な
・APIキー
・検索エンジンキー
を取得する必要があります。
まずAPIはこちらから取得します。
https://console.developers.google.com/?hl=JA
プロジェクトを作成し、認証情報を作成して、APIを有効化するという手順のようです。
認証情報→プロジェクト作成→認証情報作成で、APIキーが作成されます。
キーの制限は無くても大丈夫みたいです。
次に検索エンジンキーを取得します。
https://cse.google.com/cse/
にて、Google Custom Search Engineを作成します。
Addをクリックして作成してください。
設定はこんな感じで良いと思います。画像検索をオンにするのを忘れずに。
詳細→検索エンジンIDをクリックで検索エンジンIDを取得できます。
さて、必要なものは揃ったのでアプリを作っていきましょう。
まずは先程配置したImageViewとTextFieldを、Storyboardからコントロール+ドラッグ&ドロップでコードのほうに引っ張って、適当な名前をつけてOutlet接続します。
ButtonのほうはAction接続をし、タップされた時の処理を{}内に書いていけるようにします。
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接続します。
ImageViewは、デフォルトではタッチが効かないようになっているため、Attribute Inspector→View→Interactionの、User Interaction Enabledにチェックを入れましょう。
また、画像がImageViewの正方形の中に、縦横比はそのままで、大きく表示されるように、Content ModeをAspect Fitに設定しておきます。
準備はできたので、残りのプログラムを書きます
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に。
Google Custom APIの無料版は、1日100クエリまでの制限があるみたいです!!!