Paradigm Shift Design

ISHITOYA Kentaro's blog.

ストレージサービスMinusのライブラリMinusConnectを作った

まぁ、人間追いつめられるととんでもない方向に飛んでいってしまうもので。


無料で容量10GBのオンラインストレージサービス「Minus」が便利*二十歳街道まっしぐらとかで紹介されていたMinusですが、なんとなくキャッチーっぽいのでtottepostに実装してみました。

せっかちな人向け

リポジトリkent013/MinusConnect · GitHub
全部実装してません。User情報、Folder/Flie一覧とFolder/File作るのだけサポートしてます。


とりあえず、何となく使えればいい人向け。
ただまぁ、tottepostには載っちゃってるんでそれくらいの実装はしてます。

はじめに

tottepostに載せるために、まずすること。SDKを探すことですね。
公式には…ない…と。
githubにも…あった!hechien/ObjectiveMinus · GitHub


ん、一年更新がない…MinusのAPIはどうも最近Version2になったようで、PythonRubyPHPC#に関しては公式のgitリポジトリでサポートされてるんだけど、iOS版がない…公式でiPhoneアプリ作ってるのに公開してないってことは、まぁ大人の事情かな。


で、まぁ、Minus.com API v2 Reference — Minus API 2.10 documentationを見るに、RESTでかつOAuth2.0ってことで、DropboxAPIとか簡単そうだったし、いけるだろってことで作りはじめました。

使い方

kent013/MinusConnect · GitHubからcloneしてください。

1, ソースをプロジェクトに追加

MinusConnectUtilディレクトリの中身を適当にプロジェクトの中に入れる。

2, ライブラリをプロジェクトに追加

MinusConnect/Libraries at master · kent013/MinusConnect · GitHubの中身を適当にプロジェクトの中に入れる。
(ASIHTTPClientとかヘビーすぎるので、そのうち変えます)

3,コードを書く

サンプルコードは、MainViewController.hMainViewController.mを見てください。


3.1, 宣言

#import "MinusConnect.h"
@interface MainViewController : UIViewController<MinusSessionDelegate, MinusRequestDelegate>{
    __strong MinusConnect *minus_;
}
@property (nonatomic, readonly) MinusConnect *minus; 
@end


3.2, 初期化

MinusConnectクラスを以下のように初期化する。MINUS_CLIENT_IDとMINUS_CLIENT_SECRETはMinusのコンタクトフォームで申請してください。メールでくれます。アナログです。Co-FounderのJohn Xieさんからメールがきます。

//create instance of MinusConnect
minus_ = [[MinusConnect alloc] 
              initWithClientId:MINUS_CLIENT_ID
              clientSecret:MINUS_CLIENT_SECRET 
              andDelegate:self];


3.3, ログイン

次のようにしてログインします。なんというかOAuth2.0の意味あんのかとか思っちゃいますが、ユーザー名とパスワードを生で渡します。EndpointはHTTPSなんで、まぁ...。使える権限のリストはScopesを見てください。

[minus_ loginWithUsername:MINUS_USERNAME 
                 password:MINUS_PASSWORD 
            andPermission:[NSArray arrayWithObjects:@"read_all", @"upload_new", nil]];

レスポンスはMinusSessionDelegateを実装すれば取得できます。基本通知です。


3.4, リクエスト送信

以下のような感じでリクエスト送れます。

[minus_ activeUserWithDelegate:self];
[minus_ userWithUserId:@"kent013" andDelegate:self];
    
[minus_ folderWithFolderId:@"bfTQDBcmP" andDelegate:self];
[minus_ foldersWithUsername:@"kent013" andDelegate:self];
[minus_ createFolderWithUsername:@"kent013" name:@"test" isPublic:NO andDelegate:self];

[minus_ filesWithFolderId:@"bfTQDBcmP" andDelegate:self];
[minus_ fileWithFileId:@"C4KkzgTMA2a9" andDelegate:self];
UIImage *image = [UIImage imageNamed:@"sample1.jpg"];
NSData *data = UIImageJPEGRepresentation(image, 1.0);
[minus_ createFileWithFolderId:@"bfTQDBcmP" 
                       caption:@"test image" 
                      filename:@"sample1-1.jpg" 
                          data:data 
               dataContentType:@"image/jpeg" 
                   andDelegate:self];


3.5, レスポンスの処理

MinusRequestDelegateを実装してください。request.tagsにkMinusRequestなんたらなNSStringが入っているので、それで処理分けしてくださいませ。

-(void)requestLoading:(MinusRequest *)request{
    NSLog(@"start request");
}
- (void)request:(MinusRequest *)request didReceiveResponse:(NSURLResponse *)response{
    NSLog(@"did received response");
}
- (void)request:(MinusRequest *)client 
              didSendBodyData:(NSInteger)bytesWritten 
            totalBytesWritten:(NSInteger)totalBytesWritten
    totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite{
        NSLog(@"progress:%f", (float)totalBytesWritten /
                              (float)totalBytesExpectedToWrite);
}
- (void)request:(MinusRequest *)request didFailWithError:(NSError *)error{
    NSLog(@"request failed with error:%@", error.description);
}
- (void)request:(MinusRequest *)request didLoad:(id)result{
    if([request.tag isEqualToString:kMinusRequestActiveUser]){
        NSLog(@"active! %@", result);
    }else{
        NSLog(@"did request loaded %@", result);
    }
}
- (void)request:(MinusRequest *)request didLoadRawResponse:(NSData *)data{
    NSLog(@"did request loaded(raw)");         
}


あとは、まぁ、適当実装なのでソースコード読んでください。

余談

サンプルプロジェクトをGumろうかとおもったけど、面倒いので止めましたw


機能が足りないな!実装ダセェな!っておもったらfolkお願いします。いや、むしろ別レポジトリで結構です...使いますから教えてください…

iPhone用の簡単写真共有アプリtottepostをリリース

てぃす!


すでにご存知の方もいらっしゃると思いますが、去年の年末から今年にかけて作ってきた
「1タップで写真共有 - tottepost」
というアプリが本日、App Storeでリリースされました!


UPDATE
現在、1.1.1で、カメラのついたiPadでも利用可能です。


http://github.com/kent013/tottepost/raw/master/AppStore/screenshot3_en.png:image:left:w160
http://github.com/kent013/tottepost/raw/master/AppStore/screenshot4_en.png:image:w160



1タップで写真共有 - tottepost - ISHITOYA Kentaro


ウリは、「tottepostは写真をどえりゃー簡単に投稿できるアプリです。撮影ボタンを1回押すだけで、あらかじめ設定したtwitterfacebookのようなサービスに次々と投稿していきます」です。


設定画面を開いて、サービスの設定をしておけば連写してもガンガンアップロードできます。
現在サポートしているサービスは、Facebook / Twitter / Flickr / Dropboxです。
Twitterの場合は並列でアップロードするとアップロードに失敗することが多いので、シーケンシャルにアップロードされます)
Facebookでは、設定画面でFacebookをタップするとアルバムを選択できます)


コメントをONにしていると、プレビューが開いてコメント入力できますが、基本的にガンガンアップロードするためのアプリです。


設定画面にフィードバックというボタンがあるので、バグや要望があればお気軽にご報告ください。
また、facebookページへのフィードバックもお待ちしております。


ココまでが宣伝で、あとは開発の経緯とかをだらだら書きます。

開発の経緯

2011年の12月初旬頃、名大の植え込みに咲いていた牡丹の花がとてもきれいでした。
なのでAndroidで写真を撮りました。Facebookにあげようかなってんで、写真フォルダを開いて、その他メニュー開いて、共有メニュー(Share in Facebookが2つあったりする)を押して、コメント入力してアップロードしたんですが…失敗に終わり、再度同じ作業をしたところで電波がないことに気がついた訳ですよ。


もう、HTC Evoをへし折ろうかと。


そこで、思った訳ですね。
「撮ったらアップロードできるアプリが欲しい!」
と。


おなじく2011年の12月初旬頃、僕は妹に「Facebookにはいれ」としつこく要求していました。まめ(猫)の写真をアップロードして欲しかったんです。妹の近況はどうでもいいっちゃいいけれど、猫狂いとしては、まめの近況が知りたいわけですよ。で、妹がFacebookを始めたんですが、いっこうにまめの近況がアップロードされてこない。


もう、毎朝、催促の電話をしてやろうかと。


そこで、思った訳ですね。
「『これを立ち上げて、このボタン押せばFacebookにあがるから』って説明すればいいだけのアプリが欲しい!」
と。


なので、tottepostのコンセプトは

  • 最短1タップで写真がアップロードできる
  • 設定さえしてしまえば、おばあちゃんでも使える
  • アップロードに失敗してもあとでやり直してくれる
  • エフェクトとか凝ったものはいらない


というものです。それから研究室の後輩でiOS開発ができて比較的切羽詰まっていない、@に声をかけて、実装することになりました。

名前の話

「撮って出し」って英語でなんていうの?っていう名前です。最初はshot and postとかshoot and postだとか言ってたんですが、「とってポスト」でいいじゃないかと誰かが言ったので、tottepostになりました。


一応、英語をメイン言語に設定してあるんですが、意味分かんないだろうなw

実装の話

あ、実装の話です。呪文です。


まず、tottepostはオープンソースで公開しています。

理由は色々ありますが、正直、アプリ自体があまり売れると思っていないので、それよりはソースコード公開してるってことの方が重要だろう。ということで。NatsuLiphoneの問題とかもあって微妙なところはありますが、真似してApp Storeに出るようなことがあるのならば、むしろかなりの成功だと思っています。


写真を撮影する部分に関して、最初はUIImageViewを使っていましたが、AVFoundationを使っています。
UIImageViewだとUIImageしか取得できないのと、UIImageJpegRepresentationを呼び出さないとNSDataにできないので、カメラのEXIF情報とかが飛んでしまうためです。


投稿する部分は、PhotoSubmitterというライブラリを作りました。tottepostのリポジトリ内に入っています。

ドキュメントとか作ってないのであれですけど、基本的には

//OAuth認証開始
[[PhotoSubmitterManager submitterForType: PhotoSubmitterTypeTwitter] login];
//すでにログインしている場合
[[PhotoSubmitterManager submitterForType: PhotoSubmitterTypeTwitter] enable];

とかしてOAuth認証が走って、ログイン完了していれば

PhotoSubmitterImageEntity *photo = [[PhotoSubmitterImageEntity alloc] initWithData:data];
[[PhotoSubmitterManager sharedInstance] submitPhoto:photo];

とNSDataを渡してPhotoSubmitterImageEntityを作ってManagerに渡せば、すでにログインして有効になっているサービスに非同期で写真をアップロードすることができます。上記の場合はTwitterとDropboxですね。


基本的には、写真とって投稿するだけなので、実装はそれだけなのですが、細かい部分で色々あって大変でした。

にその苦労の片鱗がにじんでいるかと思います…

App Store関係

アプリで利用されているイメージは、Glyphish Pro 3というアイコンセットを使わせてもらっているのですが、アイコンセットに含まれているCloudマークのアイコン、iCloudの雲と全く同じなんですね…それもそのはず、AppleがGlyphishを使ってるんです.


http://github.com/kent013/tottepost/raw/master/tottepost/Resources/Images/icon@2x.png:image:lefthttp://images.apple.com/jp/icloud/images/overview_title.png:image:w128


なので、@が雲マークをチョロチョロ改変してアイコンにしました。In ReviewからReady for Saleまで4日でした。

今後の予定

Evernoteはすでにリポジトリでサポートされていますので、次期アップデートに含まれます。また、Picasaに関しても実装中です。Mixiは必要だという人が一人でもいれば実装します。


エフェクトは、つけません。
ただし、Evernote向けに矩形認識、アフィン変換、コントラスト調整は@にがんばってもらおうかなと思っています。


余談ですが、当面の目標は売り上げでiPhone4Sを買うことです(笑
現状、デバッグはtouchで行っており、iPhoneデバッグは研究室の後輩の4/4Sを奪って行っているので(笑


というわけで長くなりましたが、tottepostをよろしくお願いいたします!

Evernote APIのラッパ、EVNConnectを作った

ヒーホー。


Evernote APIの自家製ラッパを作りました。
githubにおいてあります。kent013/EVNConnect · GitHub


- 注1:thriftで生成されたevernote-apiの実装の都合上、リクエストそのものは同期です。(追記:非同期もサポートしました)

せっかちなひとむけ

  1. kent013/EVNConnect · GitHubをgit clone/ダウンロード
  2. ENVConnect/ENVConnectをコピー
  3. ライブラリの設定をする。
    • oauthconsumer/KissXMLはソースをコピーするだけじゃ駄目なのでドキュメント読んで。
  4. URL schemeの設定をする
  5. コード書く

はじめに

自分的に次はEvernoteだったので、とりあえずEvernote APIとか読んでたんですが、API呼び出しに必要なauthTokenの取得コードが

EDAMAuthenticationResult* authResult =
  [userStore authenticate:username :password : consumerKey :consumerSecret];

となってて、usernameとpasswordを入力に与えないといけないみたいで。
正直、UI考えたりするのが面倒くさいので、ううーっとなっていました。


で、いろいろ調べてたら(OFFLINE) Community Messageという投稿がみつかりました。


Julien Boedecさんという方が、OAuthが終わったときに取得できるshardIdとauth_tokenを使えばuserstore初期化する必要ないよ。アクセス先の、shardIdとauthTokenさえ分かればnoteStore使えるよ。


的な事を言ってたので、おおお、とおもい、やってみた次第です。

機能

1, OAuth認証
OAuthConsumerを使いました。
私はiPhoneでしか使わないし、マルチタスクサポートしてないiOSは対象外なので、ダイアログは実装してません。

[evernote_ login]

と書くと、Safariに切り替わって認証が始まります。


2, Request Delegate

EvernoteのAPI実装はThriftを使っているんですが、それに実装されているTHTTPClientは同期的なリクエストをしていました。

NSURLConnectionのsendSyncronousRequestはdelegateを受け取ってくれなくて、アップロードの進捗とかを外部から知る事ができません。


なので、THTTPClientを継承したクラスでasynchronousなリクエストを送り、delegateを設定できるクラスを作りました。ただし、EDAMNoteStoreの実装がsynchronousなリクエストを前提としているので、アップロードが完了するまで処理をブロックするような実装をしました。なので、UIの描画などがおかしくならない様に呼び出し側できちんと実装してあげる必要があります。


追記:結局,自分が利用する用途ではNSOperation中で上記実装を動かそうと思っていたのですが,どうもうまく行かないので,sendSynchronousRequestを送るラッパメソッドと,非同期な普通にNSURLConnectionを使った実装を用意しました.が,正直createNoteしか私は使わないので,ラッパはそれしか実装していません.


ただし,非同期に動作させるための実装はEvernoteNoteStoreClient.mに書いてあるので,必要ならば比較的簡単に実装できるかと思います.

@protocol EvernoteRequestDelegate <NSObject>
@optional
- (void)requestLoading:(EvernoteRequest*)request;
- (void)request:(EvernoteRequest*)request didReceiveResponse:(NSURLResponse*)response;
- (void)request:(EvernoteRequest*)request didFailWithError:(NSError*)error;
- (void)request:(EvernoteRequest*)request didLoad:(id)result;
- (void)request:(EvernoteRequest*)request didLoadRawResponse:(NSData*)data;
- (void)request:(EvernoteRequest*)client 
    didSendBodyData:(NSInteger)bytesWritten
    totalBytesWritten:(NSInteger)totalBytesWritten 
    totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite;
@end

のようなNSURLConnectionDelegate/NSURLConnectionDataDelegate的なdelegateを用意しています。


3, EDAMNoteStoreのラッパ

適当にWrapしましたが、私が必要な物しか実装してない上に、未テストなものが多いので、必要なら実装/デバッグしてください。現状サポートしているAPIEvernoteRequestにあります。

使いかた

  1. EVNConnect/EVNConnect以下のソースをコピー
  2. ライブラリの設定
  3. URL schemeの設定をする
  4. コードを書く


基本的にはEvernoteクラスのインスタンスを作って、ログインして、Request取得して実行って感じです。

#import "EVNConnect.h"
@interface MainViewController : UIViewController<EvernoteSessionDelegate, EvernoteRequestDelegate>{
    __strong Evernote *evernote_;
}
@property (nonatomic, readonly) Evernote *evernote;
@end

みたいに宣言しておいて、

evernote_ =
        [[Evernote alloc] initWithAuthType:EvernoteAuthTypeOAuthConsumer
                               consumerKey:EVERNOTE_CONSUMER_KEY
                            consumerSecret:EVERNOTE_CONSUMER_SECRET
                            callbackScheme:@"evnconnecttest://authorize"
                                useSandBox:YES
                               andDelegate:self];

とかしてインスタンスを作ります。consumerKey/consumerSecretはEvernoteで申請した奴で、callbackSchemaはProjectのInfoのURL Typeで指定した奴です。
そしたら、

[evernote_ loadCredential];

とすると保存されたauthTokenとshardIdなどが読み出されます。
次に、

[evernote login]

とすると、loadCredentialで読み出された情報が無効なとき、safariに遷移して認証が行われるはずです。


ここまでで、とりあえず認証は終わりですがcredentialをsaveしたりclearしたりするために、EvernoteSessionDelegateをimplementsして

-(void)evernoteDidLogin{
    [evernote_ saveCredential];
}
- (void)evernoteDidLogout{
    [evernote_ clearCredential];
}
- (void)evernoteDidNotLogin{
    [evernote_ clearCredential];
}

などとしておくと良いでしょう。


最後に、同期的なリクエストを送る場合には,

EDAMNotebook *notebook = [evernote_ notebookNamed:@"test"];

非同期なメッセージを送る場合には,

EDAMResource *resource1 = 
[evernote_ createResourceFromUIImage:[UIImage imageNamed:@"sample1.jpg"]];
    
[evernote_ createNoteInNotebook:notebook 
                          title:@"testnote" 
                        content:@"testnotemogemoge" 
                           tags:[NSArray arrayWithObjects:@"Photo", @"Bear", nil]
                      resources:[NSArray arrayWithObject:resource1]
                    andDelegate:self];

とすればいいです.

現状サポートしているAPIEvernote.hを見てください。

以上〜。

行列演算ライブラリEigenをiOSで利用する

喪中です。ことよろ。
まーにあっくまーにあっくー。


活性拡散アルゴリズムというのがありまして、それをiOS上で2次元配列を使って計算していたのだけれども、速度が遅いこと、行列演算でバグが混入する可能性があることから、ライブラリを使って実装したいなと思いました。

せっかちなひとむけ

  • Eigenからダウンロード
  • バージョンは3.0.4以降を使う
  • Eigenをxcode経由でプロジェクトに追加しない
    • 解凍したディレクトリのEigenをプロジェクトのソースコードから見える位置にFinderで追加する
  • Eigenを利用するソースコードのFile TypeをObjective-C++ sourceにする
  • #include "Eigen/Core"
  • using namespace Eigen;

Eigenライブラリとは

iOSにはAccelerate.Frameworkというのがあって、行列演算、デジタル信号処理、大数処理、画像処理なんぞができるそうです。

なんかをみる限りいろいろできそうで、行列演算に関してはBLASLAPACK使ってるので早いですよとか書いてある。ので、これを使うのを前提に調べていたのだけれど、行列の定義がポインタだし、乗算したいだけなのに関数呼ばないとならないし、オブジェクト指向的では全くないので、手をのばせていませんでした。


で、ググってたら、

に、Dean Poveyさんという方の

Eigen is a lightweight C++ linear algebra package which provides could matrix, vector and quaternion support. While I have not used it in anger, it seems to have a much nicer interface than BLAS/LAPACK and supports a bunch of things those libraries don't


EigenはライトウェイトなC++線形代数パッケージで、行列・ベクトル・クォータニオンをサポートしてるよ。他のBLAS/LAPACKライブラリがサポートしているインターフェースよりずっとよくてイライラしないよ。(超訳

とかいう素敵なコメントを発見し、

にたどり着きました。


OverViewの所を読んでもらえれば特徴はわかると思いますが、Requirementsのほうが重要かと思います。

Requirements
Eigen doesn't have any dependencies other than the C++ standard library.

We use the CMake build system, but only to build the documentation and unit-tests, and to automate installation. If you just want to use Eigen, you can use the header files right away. There is no binary library to link to, and no configured header file. Eigen is a pure template library defined in the headers.


EigenはSTL以外のC++ライブラリに依存していません。

CMakeを使っていますが、ドキュメントの生成、ユニットテスト、それからインストールの自動化にのみ使っています。Eigenを使いたいだけならヘッダファイルがそのまま使えます。ライブラリをリンクしたり、ヘッダを設定したりする必要はありません。Eigenはヘッダに定義された純粋なテンプレートライブラリです。(超訳

つまり、iOS上でもEigenを突っ込めば使えるということです。


で…問題は実際にどうやって使うの!?ってことなんですが、それがわからなくて一苦労しました。結果からいうと、ファイルタイプさえ設定すれば簡単に使えます。

xcodeで使う

まず、Eigenから3.0.4以降のバージョンをダウンロードしてきます。それ以前だとiOSでエラーがでるようです。

解凍したディレクトリの中の「Eigen」というディレクトリが、Eigenライブラリの本体です。


まず、パスの通った場所にこのEigenディレクトリをおきます。システムのincludeに入れれば、それこそ何も考えずに、

#include <Eigen/Core>

と書けば使えちゃいます。


しかしながら、リポジトリにコミットできた方がチームで開発している場合には有利でしょうから、プロジェクトのパスが通ったところにEigenライブラリをおきましょう。


例えばsampleプロジェクトをxcodeで作ると

- sample
|- sample.xcodeproj
|- sampleTexts
|- sample

という構造になるかと思います。sample/sampleの中に、Eigenディレクトリをコピって入れれば良いです。

ただし、このとき、xcodeから追加しないようにしてください

[WARN]Warning: Multiple build commands for output file /Users/ishitoya/Library/Developer/Xcode/DerivedData/sample-cdtjyyonwkgkupadfkjaxzadhzaa/Build/Products/Debug-iphonesimulator/sample.app/CMakeLists.txt

とかいう警告がたくさんでたりします。

で、実際にコードを書くわけですが、何も考えずに

#import "TestEigen.h"
#import "Eigen/Core"

@implementation TestEigen
@end

とか書くと、

In file included from /Users/ishitoya/Documents/sample/sample/Eigen/Core:35:
In file included from /Users/ishitoya/Documents/sample/sample/TestEigen.m:10:
/Users/ishitoya/Documents/sample/sample/Eigen/src/Core/util/Macros.h:187:5: error: unknown type name 'namespace' [1]
namespace Eigen {
^
/Users/ishitoya/Documents/sample/sample/Eigen/src/Core/util/Macros.h:187:20: error: expected ';' after top level declarator [1]
namespace Eigen {
^
;
fix-it:"/Users/ishitoya/Documents/sample/sample/Eigen/src/Core/util/Macros.h":{187:20-187:20}:";"
In file included from /Users/ishitoya/Documents/sample/sample/TestEigen.m:10:
/Users/ishitoya/Documents/sample/sample/Eigen/Core:144:10: fatal error: 'cerrno' file not found [2]
#include
^
3 errors generated.

というエラーがでます。でobjcでc++を使ったことがない私は、ここで詰まり、なんだEigen使えねぇんじゃねぇか!といって、またググる訳で…


原因はEigenのバグではなく、私の無知でした。
XCode4なら画面右上のViewからUtilities Viewを開いて、Identity and TypeのFile Typeを「Default-Objective-C source」から「Objective-C++ source」にかえれば、エラーが消えます。

Eigenの使い方

あとは、公式のサンプルがかなり充実しているので、

を読めば大体わかると思います。日本語だと

がとてもわかりやすかったです。


私のようにObjective-C++になれていない場合のTipsとしては、

#include "Eigen/Core"
#include <iostream>

using namespace std;
using namespace Eigen;

のように、デバッグのためiostreamヘッダを読み込んでおくことと、namespaceを使いますよ宣言をしておくことくらいでしょうか。

VectorXf v1 = VectorXf::Ones(5);
cout << v1 << endl;

とかして出力を得ることができます。


サンプルと呼べるかどうかわかりませんが、kent013/SpreadingActivationCalculator · GitHubに、Eigenを使ったプロジェクトをおいておきます。SpreadingActivationCalculator/SpreadingActivation/SpreadingActivationCalculator.m at master · kent013/SpreadingActivationCalculator · GitHubがEigenをちょろっと使ってるコードです。


以上〜

json-frameworkを含んでいるプロジェクトでDropboxSDKを使う時の注意点

だんだんマニアックさが増してきていますが,気にしないで.

ld: duplicate symbol _OBJC_IVAR_$_SBJsonBase.errorTrace

とか言われた時の対処法です.
状況は,プロジェクトでもjson-frameworkを使っていて,ライブラリにもjson-frameworkが使われているときに問題になります.


1, Projectをクリック
2, Build Settingsをクリック
3, Add Build Settingsをクリック
4, EXCLUDED_SOURCE_FILE_NAMESと入力
5, ダブルクリックして,JSON.h SBJSON.h SBJSON.m SBJsonBase.h SBJsonBase.m SBJsonParser.h SBJsonParser.m SBJsonWriter.h SBJsonWriter.m NSObject+SBJSON.h NSObject+SBJSON.m NSString+SBJSON.h NSString+SBJSON.m
と入力


で直ります.
ただし,json-frameworkのバージョンが自分のものとフレームワークのものが違う場合問題が起きます.その際は頑張ってください...orz


しかし,このEXCLUDED_SOURCE_FILE_NAMESは,どこ由来なんだろう.公式っぽい情報が見当たらない.


参考

Twitter.frameworkでアップロードプログレスを取得する

前回と同じようなネタですが、
Twitter.frameworkを使ってイメージをポストする際に、公式のサンプルに従うとアップロードの進捗を知ることができないので、それをできるようにしようという話です。


公式のドキュメントには、

- (void)performTWRequestUpload
{
    NSURL *url = 
    [NSURL URLWithString:
            @"https://upload.twitter.com/1/statuses/update_with_media.json"];
    TWRequest *request = 
    	[[TWRequest alloc] initWithURL:url parameters:nil 
    		requestMethod:TWRequestMethodPOST];
    [request setAccount:[self.accounts objectAtIndex:0]];
    UIImage *image = [UIImage imageNamed:@"larry.png"];
    NSData *imageData = UIImagePNGRepresentation(image);
    [request addMultiPartData:imageData 
    		withName:@"media[]" type:@"multipart/form-data"];
    NSString *status = @"just setting up my twttr #iOS5";
    [request addMultiPartData:[status dataUsingEncoding:NSUTF8StringEncoding] 
    		withName:@"status" type:@"multipart/form-data"];
    [request performRequestWithHandler:
    ^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
    	NSDictionary *dict = 
    	(NSDictionary *)[NSJSONSerialization 
    		JSONObjectWithData:responseData options:0 error:nil];
    		dispatch_async(dispatch_get_main_queue(), ^{
    		});
    }];
}

という、短いコードが載っています(コメントは削除しました)がこれだとperformRequestWithHandlerがレスポンスを取得したときに、ブロックに入るので進捗が分かりません。


で、

には、

Alternatively, you can use the signedURLRequest method to create a request that you send using an NSURLConnection object.

と書いてあって、signedURLRequestをつかってリクエストするといいよとちょろっと書いてあります。このプロパティ、ググってみれば分かるのですが殆ど全く情報がありません。


performRequestWithHandlerの直前でsignedURLRequestの内容をみてもらえれば分かるのですが、TWRequestを使って作成された発行される前のNSURLRequestが入っています。なので、これを直接使えばNSURLConnectionをつかってリクエストできます。つまり、

NSURLConnection *connection = 
      [[NSURLConnection alloc] initWithRequest:request.signedURLRequest delegate:self];

とすれば、performRequestWithHandlerと同じ動作をします。公式サイトの例ならばイメージがポストされます。当然、NSURLConnectionDelegateやNSURLConnectionDataDelegateを実装して、その後の処理することができます。


ただし、不勉強なので原因は分かりませんが、ブロックの中で上記コードを動かすとDelegateメソッドが呼ばれません(多分スレッドが違うから)。なので、dispatch_asyncで呼び出すかperformSelectorOnMainThreadを使って呼び出す必要があります。私の実装では


Delegateを実装するクラスでNSURLDataDelegateを実装(delegateはimplementsなのか知らん?)

@interface TwitterPhotoSubmitter : 
PhotoSubmitter<PhotoSubmitterProtocol,
 NSURLConnectionDataDelegate,
 NSURLConnectionDelegate, 
 UIAlertViewDelegate>
@end

実装は次のように

- (void)startConnectionWithRequest:(NSURLRequest *)request{
    NSURLConnection *connection = 
      [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

NSURLConnectionを生成するメソッドを用意し,

[self performSelectorOnMainThread:@selector(startConnectionWithRequest:) 
                       withObject: request.signedURLRequest 
                    waitUntilDone:NO];

として、ブロックの中でTWRequestを作り終わったあたりで呼び出します。


最後に、NSURLConnectionDataDelegateのメソッドを

- (void)connection:(NSURLConnection *)connection
   didSendBodyData:(NSInteger)bytesWritten 
 totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite{
    NSLog(@"%f", (float)totalBytesWritten / (float)totalBytesExpectedToWrite);
}

とか実装すればいいです。


APIの仕様、それぞれに採用している方法が違って、複数のサービスを使うと思うとまじで面倒くさい...

せんでん

1タップで写真共有tottepost
カメラのついたiPad/iPhoneで撮った写真を、その場でFacebook/Mixi/DropboxなどのサービスにアップロードできるtottepostというiOSアプリを開発しています!詳しくは、iTunes App Storeをご覧ください。


ご購入はこちら!
1タップで写真共有 - tottepost - ISHITOYA Kentaro

Facebook iOS SDKでRequestのプログレスを取得する

久々に書くネタができた。

Facebook iOS SDKを使って

- (void)submitPhoto:(UIImage *)photo comment:(NSString *)comment{
    NSMutableDictionary *params = 
      [NSMutableDictionary dictionaryWithObjectsAndKeys: 
       photo, @"picture", 
       comment, @"caption",
       nil];
    [facebook_ requestWithMethodName:@"photos.upload"
                          andParams:params
                      andHttpMethod:@"POST"
                        andDelegate:self];

}

的なリクエストを送ったとき、アップロードプログレスを表示したいですよね。しかしながら、純正のFBRequestDelegateではプログレスを知ることができないようです。


johnmphさんがこの要求を実装してらっしゃいました。

ただ、純正のFBRequestに手を加えていたので、手を加えずに実現してみました。

使い方は、

@interface FacebookPhotoSubmitter : NSObject<FBRequestWithUploadProgressDelegate>{
    __strong Facebook *facebook_;
}

の様にして、

- (void)request:(FBRequest *)request 
didSendBodyData:(NSInteger)bytesWritten 
totalBytesWritten:(NSInteger)totalBytesWritten 
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite{
    NSLog(@"%f", (float)totalBytesWritten / (float)totalBytesExpectedToWrite);
}

とすればいいです。


しかし、Categoryって便利だなぁ。


追記

の方が詳しかった.ってか検索能力が低くて引っかからなかっただけだなぁ….FBRequestDelegateをextendsしてるところが違うだけ.