Paradigm Shift Design

ISHITOYA Kentaro's blog.

ARC / 非ARCソースファイルのコンパイル時に設定が適合しない場合、エラーを出す方法

PhotoSubmitterというライブラリを作っています。

このライブラリ自体はARCなのです。
でも、サードパーティライブラリがARCだったり非ARCだったりして、"-fno-objc-arc"フラグを設定すべきファイルがどれなのかわからなくなってしまっていました。


SO先生に聞くと、コンパイラフラグでコンパイル時に正しく設定されているかを確かめる方法を教えてくれました。以下のコードのいずれかをどこかに埋め込むと、コンパイル時にエラーを出せます。


非ARCコードの場合

#if __has_feature(objc_arc)
#error This file must be compiled with Non-ARC. Use -fno-objc_arc flag (or convert project to Non-ARC)
#endif

ARCコードの場合

#if ! __has_feature(objc_arc)
#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif

で、これをproject.pbxprojに書いてある情報を元にソースコードに埋め込むコードをかいて設定しました。
Objective-c ARC/Non-ARC compiler flag adder for tottepost — Gist


ってわけで、メモでした。



せんでん

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


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

Xcode4.3にすると出るカテゴリに関する警告の消し方

Xcode4.3にすると、

Category is implementing a method which will also be implemented by its primary class

というワーニングが出てうざったいです。カテゴリを親クラスのメソッドをオーバーライドするために使うなという警告のようです。


自分で書いたコードなら直せば良いですが、そうでないなら

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
- (void)addRoundedRectToPath:(CGRect)rect 
                     context:(CGContextRef)context 
                   ovalWidth:(CGFloat)ovalWidth 
                  ovalHeight:(CGFloat)ovalHeight {
#pragma clang diagnostic pop

とかしておけば、警告が消えます。


しかし、このwarningの種類とフラグの対応ってどこで見つければ良いんだろう...


参考:
New Xcode 4.3 compiler warnings - categories - iPhone Dev SDK Forum

せんでん

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


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

iOSアプリ開発者が知らないと損するライブラリ7選

こういうキャッチーな*1タイトル付けてみたかった!

tottepostの開発中に役に立ったライブラリを7つ列挙します。
かなりジャンルバラバラですが紹介&メモって事で許してください。全部tottepostで使ってます。

1, SVProgressHUD

UIActivityIndicatorViewって出したり消したりするのにインスタンスの管理が面倒で、見た目も微妙ですよね。
SVProgressHUD*2は、扱いが簡単でカッコいいUIActivityIndicatorViewです。


http://f.cl.ly/items/231Y2A0t0t1J0B0r3N0p/svprogresshud3.png:image:w600


リポジトリは、samvermette/SVProgressHUD · GitHub。ライセンスはMIT。

作者のSamさんの記事はSVProgressHUD

2, AAMFeedback

これ、まじで便利です。ユーザーからの問い合わせを受け付けるフォームです。
開発者の痒いところに手が届く感じはさすがfladdictさん。


f:id:kent013:20120310162140p:image:w200:leftf:id:kent013:20120310162139p:image:w200


しかもコードは

AAMFeedbackViewController *fv = [[AAMFeedbackViewController alloc] init];
fv.toRecipients = [NSArray arrayWithObject:@"kentaro.ishitoya@example.com"];
fv.bccRecipients = [NSArray arrayWithObject:@"ken45000@example.com"];
UINavigationController *nvc = [[UINavigationController alloc] initWithRootViewController:fv];
[self presentModalViewController:nvc animated:YES];

これだけ!


送られてくるメールは

サブジェクトは選んだトピック
フィードバック本文
Device:
iPhone 4S
iOS:
5.0.1
App:
tottepost 1.0

こういうメールになります。


リポジトリは、fladdict/AAMFeedback · GitHub。ライセンスは不明。

fladdictさんによる記事は、iPhoneアプリに、ユーザーフィードバックのフォームをつけるライブラリを書いてみた。 | fladdict

3, appirater

"Rate this app”ってアラートよく見ますよね。見ません?あれを出すライブラリです。
ユーザーがアプリを30日以上使っていて、15回以上起動していたら自動で下のようなポップアップがでてきます。


f:id:kent013:20120310163848p:image:w240


それだけでなく、ボタンを設置してそのハンドラに、

[Appirater rateApp];

のようなコードを書くと、直接AppStoreに飛んでくれます。

英語、日本語、フランス語、ドイツ語に対応しています。


リポジトリは、arashpayan/appirater · GitHub。ライセンスはMIT。

作者のArash Payanさんによる記事は、 Presenting, Appirater - Arash Payan | Blog

4, FBProgressView

iOS SDK標準のプログレスバーって、でかいですよね…

もう少し狭い領域にプログレス出したいんだけど…

もうちっとかっこいいのがほしいんだけど…

って時は、これです。


f:id:kent013:20120310163849p:image:w200


tottepostでは写真アップロードのプログレス表示に使っています。


リポジトリは、dev5tec/FBProgressView · GitHub。ライセンスはMIT。

作者のHashigami Hiroshiさんによる記事は、Cocoaの日々: [iOS] カスタムプログレスバー公開

5, HPGrowingTextView

UITextViewって文字数増えると、全部が見えなくなるし、スクロールしなきゃいけないので大きめの領域を確保しなきゃいけなくて微妙ですよね。

そこへいくと、SMSのエディットボックスは、文字数が増えるとサイズが変わって使いやすいです。


で、これ、それです。


Multi-line UITextView similar to SMS from Hans Pinckaers on Vimeo.


リポジトリは、HansPinckaers/GrowingTextView · GitHub。ライセンスはMIT。作者のHans Pinckaersさんの記事は、Multi-line/Autoresizing UITextView similar to SMS-app | Hans Pinckaers

6, FBNetworkReachability

Reachabilityライブラリ、ありますよね。
appleのやつ。正直使い勝手が悪いんですね。

Reachabilityでも同様の事はできますが、FBNetworkReachabilityはそのあたりをラップしてくれているので、とても簡単に使えます。


リポジトリdev5tec/FBNetworkReachability · GitHub。ライセンスはMIT。

作者のHashigami Hiroshiさんによる記事は、 Reachabilityがバージョンアップされて使い方が少し変更されていた - Tomute’s Notes

7, ScreenStatus

iOSは画面がロックされると、画面の向きを知る方法がなくなるんです。僕らが困るような事はもうすでに誰かが困ってる訳で。
ScreenStatusは、画面がロックされていても

UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];

ってやると、加速度センサを使って画面の向きを教えてくれます。


いや、マジ助かりました!
tottepostでは画面ロック時にインターフェースと写真を回転するのに使ってます。


作者のMitsuharu Emotoさんによる記事は、iPhone:iOS4.0の回転ロックでハマったこと | mthr Blog+

ライセンスは隣人に飯をおごる事。

しょかん

SDKもそうですが、3年前に比べると様々なライブラリが公開されていて、機能を自前で実装する前にgithubで検索すると、ある程度のライブラリは見つかるようになりました。良い時代ですね。


ただ、正直言ってそういったライブラリは、"こなれていない"ことも多くて、作者の想定した使い方をしないときちんと動かない事が多いです。
そういうライブラリに手を加えて作者にメールを送るのが昔はスタンダードでした。でもそんなの今は昔。


githubってすごいな。LinusさんLinuxよりもすごい物を世の中に出したのかもしれないと3周回遅れぐらいで思いました。
良いライブラリがあったら、教えてください。


というわけで、この記事は、@ken4500に飯をおごった事を報告する記事でした。

せんでん

これらのライブラリをすべて利用したアプリ、tottepostはAppStoreで公開しています。


また、tottepostのために作ったライブラリPhotoSubmitterも、tottepostから分離して独立させました。

是非cloneして試してみてください!


よろしくおねがいします!

*1:あざとい

*2:なんでこれをHeads Up Displayというのかは分かりません…

fotolifeAPIの挙動がおかしい?

うーん... iOSでfotolife APIを呼んでるんだけど変な挙動する。


一回正しいWSSE Token作ってhttp://f.hatena.ne.jp/atomにアクセスした後、間違った適当なユーザー名と適当なパスワードでアクセスすると、結構な割合で認証に通る。しかも元のユーザのIDで認証が通ってるっぽい。


kent013で認証した後、別のユーザでログインしてみたログ。


ユーザ名:"d", パスワード:"d"
でリクエストヘッダ(改行してあります)

2012-03-10 01:07:01.332 tottepost[96207:1a303] 
{
    Authorization = "WSSE profile=\"UsernameToken\"";
    "X-Wsse" = "UsernameToken Username=\"d\", 
    PasswordDigest=\"UKsxV+dhFHnLQinj4nu/V7VrGEQ=\", 
    Nonce=\"MTMzMTMwOTIyMS4zMzA0OTQ=\",
    Created=\"2012-03-09T16:07:01Z\"";
}

そのレスポンス

2012-03-10 01:07:01.374 tottepost[96207:1a303] 
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://purl.org/atom/ns#">
  <link type="application/x.atom+xml" rel="service.post"
        href="http://f.hatena.ne.jp/atom/post" title="kent013&#39;s fotolife" />
  <link type="application/x.atom+xml" rel="service.feed"
        href="http://f.hatena.ne.jp/atom/feed" title="kent013&#39;s fotolife" />
</feed>, 

kent013のfeedが返ってくる。レスポンスのヘッダは、

{
    Age = 8412;
    Connection = "Keep-Alive";
    "Content-Encoding" = gzip;
    "Content-Length" = 198;
    "Content-Type" = "application/x.atom+xml; charset=utf-8";
    Date = "Fri, 09 Mar 2012 16:07:01 GMT";
    "Keep-Alive" = "timeout=7, max=5";
    Server = "Apache/2.2.3 (CentOS)";
    Vary = "Accept-Encoding,User-Agent";
    Via = "1.1 fotolifesquid01.hatena.ne.jp:80 (squid/2.7.STABLE6)";
    "X-Cache" = "HIT from squid.hatena.ne.jp";
    "X-Cache-Lookup" = "HIT from squid.hatena.ne.jp:80";
    "X-Framework" = "Hatena/2.0";
    "X-PageMaker" = Atom;
    "X-Server" = fotolifebackend02;
}

なんか、キャッシュにHITしてる。

二回目のリクエスト、ユーザー名:"k", パスワード:"k"

2012-03-10 01:07:18.290 tottepost[96207:1a303] 
{
    Authorization = "WSSE profile=\"UsernameToken\"";
    "X-Wsse" = "UsernameToken Username=\"k\", 
    PasswordDigest=\"tZ4EPCQGnE08ytIHjotyIapWltc=\", 
    Nonce=\"MTMzMTMwOTIzOC4yOTAzMTM=\", 
    Created=\"2012-03-09T16:07:18Z\"";
}

レスポンス。やっぱりkent013

2012-03-10 01:07:18.328 tottepost[96207:1a303] 
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://purl.org/atom/ns#">
  <link type="application/x.atom+xml" rel="service.post"
        href="http://f.hatena.ne.jp/atom/post" title="kent013&#39;s fotolife" />
  <link type="application/x.atom+xml" rel="service.feed"
        href="http://f.hatena.ne.jp/atom/feed" title="kent013&#39;s fotolife" />
</feed>,

そしてレスポンスヘッダ

{
    Age = 8429;
    Connection = "Keep-Alive";
    "Content-Encoding" = gzip;
    "Content-Length" = 198;
    "Content-Type" = "application/x.atom+xml; charset=utf-8";
    Date = "Fri, 09 Mar 2012 16:07:18 GMT";
    "Keep-Alive" = "timeout=7, max=5";
    Server = "Apache/2.2.3 (CentOS)";
    Vary = "Accept-Encoding,User-Agent";
    Via = "1.1 fotolifesquid01.hatena.ne.jp:80 (squid/2.7.STABLE6)";
    "X-Cache" = "HIT from squid.hatena.ne.jp";
    "X-Cache-Lookup" = "HIT from squid.hatena.ne.jp:80";
    "X-Framework" = "Hatena/2.0";
    "X-PageMaker" = Atom;
    "X-Server" = fotolifebackend02;
}

やっぱりHIT。MISSしたときは403が返ってくる。当然403が正しい挙動だよね...?うーん。おれの実装がおかしいのかfotolifeがおかしいのか、判断に困るな...


追記

ヘッダに

"Cache-Control" = "no-cache";

って付けたら正しく403が返ってくるようになったけど、うーん...

ソーシャル/クラウドサービスSDK、よもやま話

いろんなサービスのiOS用SDKを触った感想なぞ。
tottepostでは、Facebook/Twitter/Flickr/Dropbox/Picasa/Evernote/Minus/Mixi/Fotolifeの9つが使えます。


SDKのオレオレランキングを発表するとですね、

Dropbox > Facebook, Twitter > Flickr > Picasa > Evernote >>>>> Mixi

って感じ。
Minusは自分で作ったので対象外で、FotolifeSDK提供してないので選外。


まぁ、MixiSDKの愚痴を言うためのエントリだと思ってくださいw

苦労話

色々なサービスのSDKを使ってみましたが、SDKのコードを修正する事なしに使えたのはFlickrDropboxPicasaだけでした。


Dropboxサイコー!


Facebookは、NSURLDatadelegateのない時代のコードなので、プログレスが取得できず、カテゴリを使って拡張しました。過去の記事に詳細があります。


Twitterも、そのまま使うとプログレスが取得できないので、signedURLRequestを使う必要がありました。その辺りは、この記事に書きました。


Flickrは、objectiveFlickrをつかってます。まぁ可もなく不可もなく。


Evernoteに関しては、公開されているEDAMというAPISDKが同期的な通信しかサポートしていないので、新しくEVNConnectというライブラリを作りました。興味のある方は、EVNConnectの記事をどうぞ。


Minusは元々、iOS用のSDKがない…のでMinusConnectというライブラリを作った。MinusConnectの記事をご覧くださいませ。


Fotolifeは、AtomPubという旧時代のAPIで認証がWSSEでした。objc-atompubというライブラリを見つけたのですが、iOS向けではないので、forkしてiOS5対応にしました。コードは、kent013/objc-atompub · GitHubに置いてあります。


Picasaに関しては、GData Objective-C Client Setup in XCode 4 « Kelvin's BlogあたりにXCode4で使うための設定が書いてあります。たしかにそのまま使えたんだけども…GData APIGoogleすべてのサービスをカバーしているだけあって、ドキュメントが膨大。写真アップロードしたいだけなのに、どうやったら良いのかが分からず迷子になって時間がかかりました...Google先生もちゃんと答えてくれないしさ。


Mixiは...SDKあるんだけど、バグがあるしドキュメント日本語しかないし、Mixiアプリの説明とMixiGraphAPIの説明がごっちゃになってるし、SDKオープンソースじゃねぇし、やるきあんのかxx野郎って思いました。
最初、OAuthなのにMixiアプリ経由でしか認証できなかったので、思わず、は?って言ったのを覚えています。
たしか、先月中旬くらいのアップデートでWebViewを使った認証がサポートされましたが、バグがあったのとdelegateでリクエスト本体を渡してくれない仕様だったので直して使ってます。最新版では直ってるかもしれませんが。
まぁ、一番アレなのは、SDKに対して文句を言う場所がないことなんですがね。

ログイン周り

  • クライアントが認証に必要なUIを用意しなくて良い
  • クライアントがユーザー名やパスワードを保存しなくて良い
  • クライアントが認証方法を意識しなくていい

あたりが重要な要素かな。例えば、Dropboxだと

[[DBSessionsharedSession] link];

Facebookだと

NSArray *permissions =
  [NSArrayarrayWithObjects:@"publish_stream", @"user_location",
                           @"user_photos", @"offline_access", nil];
[facebook authorize:permissions];

だけです。PicasaMixiは、ログイン用のUIが特別に用意されているのでそれを表示する実装をしなければならないです。
そのかわり、FacebookDropboxの場合はアプリの切り替えが発生するのでその処理を書かなければならないですが。


それから、クライアント側でユーザ名とパスワードを保存しなきゃならない実装は微妙だなーって思います。認証の問題なんだけど、FotolifeはWSSEだから保存しなきゃ行けないし、MinusはOAuth2なのにPassword認証しかサポートしてないという...


あと、認証画面ね。アプリが認証画面をだす実装は、アプリの実装に影響を与える(UINavigationControllerにpushするか、presentModalViewしたいのかとか)からあんまり好きじゃないなぁ。アプリが入ってないと認証できない仕様は論外としてだね。やっぱりアプリが入っていればアプリで、入ってなければSafariで認証してくれるほうが楽。

リクエスト周り

特にDelegateが問題になるかなと思います。

  • 非同期通信
  • クライアントからリクエスト本体を知る事ができる
  • Delegateメソッドがリクエストの種類ごとに分かれている

が重要ですね。非同期通信なのはマストだと思います。Evernoteが同期通信を採用していましたが、それはEvernoteだとあまり非同期に通信する事がないからなのでしょうか。
で、Mixiがそうでしたが、クライアント側からリクエスト本体を知る事ができないと、delegateが提供されていても「通信が終わった」ことしか分からないです。

- (void)mixi:(Mixi*)mixi didSuccessWithJson:(id)data;

とか微妙すぎます。せっかくMixiオブジェクトもらえるのに、その通信がなんなのかが分からないってのはどういう実装なのか。

- (void)mixi:(Mixi*)mixi andConnection:(NSURLConnection *)connection
                      didFinishLoading:(NSString*)data;

こうなっていれば、

NSString *url = [connection.currentRequest.URL absoluteString];
NSString *method = connection.currentRequest.HTTPMethod;
if([url isMatchedByRegex:@"token"]){
}elseif([url isMatchedByRegex:@"albums/@me/@self"] &&
        [method isEqualToString:@"POST"]){
}

のように処理分けする事ができます。で、まぁ最低限、connectionとかそれをラップしたクラスがdelegateメソッドに渡されていれば処理分けできるので良いのですが、Dropboxは、

[restClient uploadFile:filename toPath:toPath withParentRev:nilfromPath:path];

に対応するdelegateとして

- (void)restClient:(DBRestClient*)client uploadedFile:(NSString*)destPath
              from:(NSString*)srcPath metadata:(DBMetadata*)metadata;
- (void)restClient:(DBRestClient*)client uploadFileFailedWithError:(NSError *)error;
- (void)restClient:(DBRestClient*)client uploadProgress:(CGFloat)progress
           forFile:(NSString*)destPath from:(NSString*)srcPath;

が用意されており、createFoloderなら、それ専用のdelegateが用意されています。
これだと確かにコードは長くなりますが、見通しはよくなりますよね。
どこに何を書けば良いのかが分かりやすいし、何より、なにを使えば処理分けできるのかを調べなくていいので楽です。

あとは、atompubなFlickrAPIAPIの実装が微妙で、postURIがアルバムのIDを知っているだけじゃ構成できなくて、postする前に問い合わせなきゃいけなくて。あと、空のアルバムを作れないってのが一番辛かった...

SDKとして

  • ソースコードをプロジェクトに追加しなくていい
  • ドキュメントが簡潔

ってところでしょうか。DropboxSDKはframeworkになっているので、プロジェクト扱いが簡単です。
静的ライブラリになっているものも良いと思います。
mファイルをプロジェクトに追加しないとならないタイプの実装は、微妙かなぁとおもいます。


ドキュメントに関しては、GDataが...いや細かくていいのかもしれないし、色々な言語で通用するドキュメントを書こうと思うとあぁなるのかもしれないけど、どこに何があるのかが分からなくなるほど膨大だと、死ねます…


FacebookEvernoteもドキュメントが微妙。一番分かりやすかったのはFlickrかな。

そのほか

Mixiさんは海外展開する気、全くないんだなーって思いました。日本語のドキュメントしかないし。っていうかGraphAPI使うのに、クレジットカード登録させるとかすごいなーって思う。まぁ、ちゃんとしてると言えば、ちゃんとしてるのか。


まぢで、SDKの共通規格をだれか書けばいいんじゃないかって思う。


デベロッパに優しい会社が増えてくれると良いなー。
ていうか、やっぱSDKOSSで開発して欲しいなと思う今日この頃です。

写真アップロードサービスの抽象化レイヤーPhotoSubmitter

tottepostを実装するために、写真を色々なサービスにアップロードする必要がありました。
で、色々検索してみたのですが、適当な物が見つからず、自分で作りました。
似たような物は、iOSSocialとかがあります。


簡単に言うと

[PhotoSubmitterManager submitPhoto:photo];

ってやると、あらかじめ設定したサービスに写真がアップロードされるライブラリです。


また、
https://github.com/kent013/tottepost/raw/master/AppStore/screenshot4_en.png:image:w200:left
https://github.com/kent013/tottepost/raw/master/AppStore/screenshot5_en.png:image:w200


のような設定を

PhotoSubmitterSettingTableViewController *settingViewController_ = 
  [[PhotoSubmitterSettingTableViewController alloc] init];
settingViewController_.delegate = self;
settingNavigationController_ = 
  [[UINavigationController alloc] 
    initWithRootViewController:settingViewController_];
[self presentModalViewController:settingNavigationController_ animated:YES];

というコードだけで出す事ができます。認証もこの画面からできるので、サービスのON/OFFや認証などのコードはほとんど書く必要がありません。

PhotoSubmitterについて

ソースコードは、
kent013/PhotoSubmitter · GitHub
にあります。


サンプルコードは、

です。tottepostはPhotoSubmitterの全機能を使っています。またPhotoSubmitterExampleは最小限の機能だけ使っています。


上記、リポジトリのREADME.mdに細かいことは書いてありますが、

[[PhotoSubmitterManager submitterForType:@"facebook"] login];

とすると、渡されたTypeに応じたログイン画面が表示されます。すでにログインされている場合には何もおきません。現在のところサポートしているのはFacebook/Twitter/Flickr/Dropbox/Picasa/Evernote/Minus/Mixi/Fotolifeの9つです。

[[PhotoSubmitterManager submitterForType:@"dropbox"] login];
[[PhotoSubmitterManager submitterForType:@"evernote"] login];

として、必要なサービスをONにしていく事ができます。サービスをONにしたあと、

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

のようにすると、ONにしたサービス全部に写真がアップロードされます。


できればtottepostから独立させてサービスをプラグイン化できるようにしたいのですが、いまいちうまい実装が思い浮かばないです…

バージョン1.1リリース!iPhone用の簡単写真共有アプリtottepost。

先ほど、かねてから準備していたtottepostの1.1がReady for Saleになりました!
今回のアップデートでは、以下の機能拡張、バグフィックスが行われています.

  • Evernote / Picasa / Minus / Fotolife / Mixiをサポート。
  • アルバムの選択機能
  • アルバムの作成機能
  • インターフェースを回転しないようにした
  • 回転を検知したときにボタンを回転するようにした
  • Twitterで複数アカウントが登録されている場合に選択できるようにした
  • アップロードの自動再開、キャンセル機能のバグを修正
  • ズームしたときに大きく位置がずれる問題への対処
  • コメント欄の文字が見にくい問題への対処

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

アップデート直後にアップデート前にログインしたサービスが使えなくなる場合がありますので、その場合はログアウトしてから再度ログインしてください。


1.0のリリースから1ヶ月、様々なコメントをいただいて致命的なバグは修正したつもりです。問題があればフィードバック、このエントリへのコメント、FacebookTwitterなどでご連絡ください!

GithubのIssues


しかし、InReviewまで5営業日、Ready for Saleまで3時間とか…