SwiftでTiqav APIを叩くビューワアプリを100行でつくったよ
2015/03/30 追記
記載しているコードは古くて動かない可能性があります。
最新の環境で動くコードは以下に置いてあります:
https://github.com/himaratsu/SwiftTiqavViewer
--
以下の記事を読んで触発されてつくってみました。
つくったもの
Tiqav viewer
Tiqavとは
言葉に応じた画像を検索できるサービスです。
登録されている画像を返すAPIが公開されているのでSwiftで使ってみました。
アプリの概要
- Storyboard 使用
- NSURLConnection で tiqav api を叩いて結果をパース
- UITableView (+Custome Cell) に結果を表示
ファイルの構成
- AppDelegate.swift
- 自動生成のまま
- ViewController.swift
- 以下で紹介する
- Main.storyboard
- ViewContoller.swiftにUITableView, UITableViewCellを追加
- TiqavCell.swift
- カスタムUITableViewCell. IBOutletをもたせただけ
コードの要点
通信 (NSURLConnection)
NSURLConnectionを使ってWebAPIにアクセスする方法です。
func reload() { // Thanks to tiqav api! ( http://dev.tiqav.com/ ) let URL = NSURL(string: "http://api.tiqav.com/search/random.json") let req = NSURLRequest(URL: URL) let connection: NSURLConnection = NSURLConnection(request: req, delegate: self, startImmediately: false) // NSURLConnectionを使ってアクセス NSURLConnection.sendAsynchronousRequest(Req, queue: NSOperationQueue.mainQueue(), completionHandler: self.fetchResponse) } // responseを処理する func fetchResponse(res: NSURLResponse!, data: NSData!, error: NSError!) { // responseをjsonに変換 let json: NSArray = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: nil) as NSArray tiqavs = [] for img in json { let imgId = img["id"] as String let ext = img["ext"] as String let imageUrl = (baseUrl + imgId + "." + ext) as String tiqavs.append(imageUrl) } // tableviewの更新 dispatch_async(dispatch_get_main_queue(), { self.tableView.reloadData() }) }
NSJSONSerialization.JSONObjectWithData
は AnyObject!
を返すので、as NSArray
などとして型を指定する必要があります。
本来であれば array か dictionary かを判定して型を指定すれば良いはずです。
ここで用いているAPIのレスポンス例は以下のような感じです。
({ ext = jpg; height = 300; id = 1b8; "source_url" = "http://jan.2chan.net/jun/b/src/1260209767367.jpg"; width = 400; })
非同期での画像の読み込み
以下のページの1つ目のやり方をswiftで実装してます。
var q_global: dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); var q_main: dispatch_queue_t = dispatch_get_main_queue(); // imageUrl に画像のURLが入っている // 非同期でimageUrlの内容を取得 dispatch_async(q_global, { var imageURL: NSURL = NSURL.URLWithString(imageUrl) var imageData: NSData = NSData(contentsOfURL: imageURL) var image: UIImage = UIImage(data: imageData) // 更新はmain threadで dispatch_async(q_main, { cell.tiqavImageView.image = image; cell.layoutSubviews() }) })
画像を非同期で取得してメインスレッドでUI更新。
GCDはそのまま素直に使えるようでスンナリ書けました。
delegate の実装宣言
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSURLConnectionDelegate { ... }
こんな感じで書きます。
UITableViewDataSource, UITableViewDelegate の接続は今回はStoryboardでやってます。
コード全文
ViewController.swift
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSURLConnectionDelegate { @IBOutlet var tableView : UITableView! let baseUrl = "http://img.tiqav.com/" var tiqavs = [String]() override func viewDidLoad() { super.viewDidLoad() self.title = "tiqav images" self.reload() } func reload() { // Thanks to tiqav api! ( http://dev.tiqav.com/ ) let URL = NSURL(string: "http://api.tiqav.com/search/random.json") let Req = NSURLRequest(URL: URL) let connection: NSURLConnection = NSURLConnection(request: Req, delegate: self, startImmediately: false) NSURLConnection.sendAsynchronousRequest(Req, queue: NSOperationQueue.mainQueue(), completionHandler: self.fetchResponse) } func fetchResponse(res: NSURLResponse!, data: NSData!, error: NSError!) { let json: NSArray = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: nil) as NSArray tiqavs = [String]() for img in json { let imgId = img["id"] as String let ext = img["ext"] as String let imageUrl = (baseUrl + imgId + "." + ext) as String tiqavs.append(imageUrl) } dispatch_async(dispatch_get_main_queue(), { self.tableView.reloadData() }) } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return 120 } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tiqavs.count } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell: TiqavCell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as TiqavCell var imageUrl = tiqavs[indexPath.row] as String cell.tiqavUrlLabel.text = imageUrl; cell.tiqavImageView.image = nil; var q_global: dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); var q_main: dispatch_queue_t = dispatch_get_main_queue(); dispatch_async(q_global, { var imageURL: NSURL = NSURL.URLWithString(imageUrl) var imageData: NSData = NSData(contentsOfURL: imageURL) var image: UIImage = UIImage(data: imageData) dispatch_async(q_main, { cell.tiqavImageView.image = image; cell.layoutSubviews() }) }) return cell; } func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { var text: String = tiqavs[indexPath.row] // show alert let alert = UIAlertController(title: "taped", message: text, preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "close", style: .Default, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) } @IBAction func reloadBtnTouched(sender : AnyObject) { self.reload() self.tableView.scrollRectToVisible(CGRect(x:0 , y: 0, width: 1,height:1), animated: true) } }
TiqavCell.swift
import UIKit class TiqavCell: UITableViewCell { @IBOutlet var tiqavImageView : UIImageView! @IBOutlet var tiqavUrlLabel : UILabel! override func awakeFromNib() { super.awakeFromNib() tiqavUrlLabel.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 0.8) } }
ソースコード
GitHubにあげました
ここまでのSwift
- Xcodeの補完がちゃんと効けば気持ちよく書けそう
- インデキシングに時間がかかってるだけという説を信じてる
- Objective-Cとあまり変わらないので慣れると書きやすい
- ! と ? の仕様が分かってない
heightForRowAtIndexPath
でstaticのcellを作って高さを返すのが何故かできない- dispatchとか使っての非同期処理はそのまま書けそう
まとめ
最初の記法だけ覚えればObjective-Cの知識でほぼ書けそうです。
Swift各地で盛り上がっててなんか祭り感あって楽しいです。