PivixからTINAMIへ
以前PixivのRSSリーダーを作りました。
このままiOSアプリに申請しようかと思ったのですが、
PixivのAPIは非公開で、「非公開APIを使ったアプリはリジェクトされる」という
情報をキャッチしましたので、APIが公開されているTINAMIで同じものを作ろうと
思いました。
それで、作りました。
下準備
TINAMIのAPIを使うためには申請する必要です。
実装にあたり直面した問題点
XML
ただ、Pixivのソレと比べて問題なのは、APIを叩いて出力されるのが
XMLだということです。JSONでもRSSでもなく。
JSONやRSSならBubbleWrapで簡単にパースできるのですが、
XMLを直にパースしようとしてもなかなかうまくいかないので、発想を転換してXMLを
JSONに変換することにしました。
で、参考にしたのがこちらの記事。
サーバーを立ち上げて、XMLをJSONに変換しております。
今回はnode.jsのフレームワークexpressを利用しました。
なぜexpressを利用したかというと、検索結果を利用したいので、
引数を自由に使えるようにするためにはやっぱりフレームワークが必要かなって。
そんなこんなでnode.jsで実装したスクリプトでサーバーを起動して
にアクセスすると、そのURLがエンドポイントになるって寸法です。
exports.server = function(req, res){
var parser = require('xml2json');
var request = require('request');
var param = req.query.p1;
var xml = 'http://api.tinami.com/content/search?api_key=TINAMIのAPI&text=' + param;
var json = '';
request(xml, function (error, response, body) {
if (!error && response.statusCode == 200) {
var options = {
object: false,
reversible: false,
coerce: true,
sanitize: true,
trim: true,
arrayNotation: false
};
json = parser.toJson(body, options);
console.log(json);
res.render('server',
{
msg: json
}
);
}
});
};
GitHubとAPIキー
また、基本的に僕はGitHubにソースコードを上げたいので
APIキーが見えるところにあるのも地味に問題でした。
それも一応は解決しました。
Motion::Project::App.setup do |app|
# Use `rake config' to see complete project settings.
app.name = 'TinamiReader'
require 'yaml'
conf_file = './config.yml'
if File.exists?(conf_file)
config = YAML::load_file(conf_file)
app.testflight.sdk = 'vendor/TestFlightSDK'
app.testflight.api_token = config['testflight']['api_token']
app.testflight.team_token = config['testflight']['team_token']
app.info_plist['TINAMI_API'] = config['tinami']['api']
env = ENV['ENV'] || 'development'
app.provisioning_profile = config[env]['provisioning']
end
end
testflight:
api_token: API TOKEN
team_token: TEAM TOKEN
development:
provisioning: '/path/to/file'
tinami:
api: TINAMI API
コードでAPIキーを参照したいときは、このように使いました。
api_key = NSBundle.mainBundle.objectForInfoDictionaryKey('TINAMI_API')
url = "http://api.tinami.com/image?api_key=#{api_key}&cont_id=#{self.item['id']}&no=1"
アプリコード
それで本丸です。
get_itemsのところがキモ。
さっきのURLから得られたJSONをBW::JSONでパースして…
って感じで、欲しい情報を格納していっています。
# -*- coding: utf-8 -*-
class TinamiViewController < UITableViewController
def viewDidLoad
super
@feed = nil
@items = []
self.view.backgroundColor = UIColor.whiteColor
@refreshControl = UIRefreshControl.alloc.init
# 更新アクションを設定
@refreshControl.addTarget(self,
action:"onRefresh",
forControlEvents:UIControlEventValueChanged)
self.refreshControl = @refreshControl
@searchBar = UISearchBar.alloc.initWithFrame(CGRectMake(0, 0, 0, 0))
@searchBar.delegate = self
@searchBar.showsCancelButton = true
@searchBar.sizeToFit
self.view.dataSource = view.delegate = self
self.navigationItem.titleView = @searchBar
# @searchBar.text = 'マナりつ'
@searchBar.text = ''
self.getItems(@feed, @searchBar)
self.buildRefreshBtn
searchBarCancelButtonClicked(@searchBar)
end
def getItems(feed, searchBar)
query = searchBar.text.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
url = "http://localhost:3000/server?p1=#{query}"
@items.clear
BW::HTTP.get(url) do |response|
if response.ok?
@feed = BW::JSON.parse(response.body.to_str)
for row in @feed['rsp']['contents']['content']
if row.nil?
break
end
@items << row
end
view.reloadData
else
App.alert(response.error_message)
end
end
return @items
end
def tableView(tableView, numberOfRowsInSection:section)
if @items.nil?
return 0
else
# @items.size
15
end
end
def tableView(tableView, heightForRowAtIndexPath:indexPath)
40
end
def tableView(tableView, cellForRowAtIndexPath:indexPath)
cell = tableView.dequeueReusableCellWithIdentifier('cell') || UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:'cell')
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator
if @items == []
return cell
end
# title
cell.textLabel.frame = CGRectMake(200, 200, 20, 30)
cell.textLabel.text = @items[indexPath.row]['title']
cell.textLabel.font = UIFont.boldSystemFontOfSize(14)
cell.textLabel.textAlignment = UITextAlignmentRight
# thumbnail
image_path = @items[indexPath.row]['thumbnails']['thumbnail_150x150']['url']
image_src = NSData.dataWithContentsOfURL(NSURL.URLWithString(image_path))
image = UIImage.imageWithData(image_src)
image_view = UIImageView.alloc.initWithImage(image)
image_view.frame = CGRectMake(5, 5, 30, 30)
cell.addSubview(image_view)
return cell
end
def tableView(tableView, didSelectRowAtIndexPath:indexPath)
WebViewController.new.tap do |c|
c.item = @items[indexPath.row]
self.navigationController.pushViewController(c, animated:true)
end
end
# 更新ボタンを生成
def buildRefreshBtn
btn = UIBarButtonItem.alloc.initWithBarButtonSystemItem(UIBarButtonSystemItemRefresh,
target:self,
action:"eventRefreshBtn:")
btn.tintColor = UIColor.redColor
self.setToolbarItems(arrayWithObjects:"btn", animated:true)
self.navigationItem.leftBarButtonItem = btn
end
# 処理中のイベント
def eventActivityIndicator
self.getItems(@feed, @searchBar)
# 処理中を、更新ボタンに切り替える
self.buildRefreshBtn
end
# 更新ボタンのイベント
def eventRefreshBtn(sender)
# 更新ボタンを、処理中に切り替える
self.buildActivityIndicator
end
# 処理中を生成
def buildActivityIndicator
activityIndicator = UIActivityIndicatorView.alloc.initWithFrame(CGRectMake(0, 0, 30, 20))
activityIndicator.startAnimating
btn = UIBarButtonItem.alloc.initWithCustomView(activityIndicator)
self.setToolbarItems(arrayWithObjects:"btn", animated:true)
self.navigationItem.leftBarButtonItem = btn
self.performSelector("eventActivityIndicator", withObject:nil, afterDelay:0.1)
end
def searchBarSearchButtonClicked(searchBar)
@searchBar.resignFirstResponder
self.getItems(@feed, @searchBar)
end
def searchBarCancelButtonClicked(searchBar)
searchBar.resignFirstResponder
end
def onRefresh
# 更新開始
self.refreshControl.beginRefreshing
view.reloadData
@searchBar.resignFirstResponder
self.getItems(@feed, @searchBar)
# 更新終了
self.refreshControl.endRefreshing
end
end
結論
そんなたいしたことはやっていませんが、一応の流れだけ。
- TINAMIのAPIを叩いて出力されるXMLをJSONにパースする
- 得られたJSONをBubbleWrapを利用してRubyMotionで使えるようなデータに落とし込む
です。
んーこの程度ではまだまだ届かぬ!!的な。