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各地で盛り上がっててなんか祭り感あって楽しいです。
