iPhone
Xcode
iOS
animation

コールバック地獄からの脱却!複雑なiOSアニメーションをPromiseの決定版ライブラリPromiseKitですっきり実装する!!!

More than 3 years have passed since last update.

by mixiappwchr

iOSなどアプリ開発において、非同期での処理は欠かせません。ネットワークの通信や思い処理の実装は多岐にわたりますが、愚直に書くと大量のコールバックにより、非常に見通しが悪くなってしまいます。

そうなるとiOSでもPromiseで書きたくなりますが、昨今人気が出てきたのはPromiseKitでしょう。

今回はこのライブラリを使って、如何にアニメーションの記述が楽になるか説明します。


PromiseKit

PromiseKit

このライブラリで一番気に入っているのは何と言ってもメソッドチェーンが非常にわかりやすくかけるという点です。

dispatch_promise(^{

return md5(email);
}).then(^(NSString *md5){
return [NSURLConnection GET:@"http://gravatar.com/%@", md5];
}).then(^(UIImage *gravatarImage){
self.imageView.image = gravatarImage;
});

例えば,ほかにPromiseでかけるライブラリBoltsと比べてみると

PFQuery *query = [PFQuery queryWithClassName:@"Student"];

[query orderByDescending:@"gpa"];
[[[[[self findAsync:query] continueWithSuccessBlock:^id(BFTask *task) {
NSArray *students = task.result;
PFObject *valedictorian = [students objectAtIndex:0];
[valedictorian setObject:@YES forKey:@"valedictorian"];
return [self saveAsync:valedictorian];
}] continueWithSuccessBlock:^id(BFTask *task) {
PFObject *valedictorian = task.result;
return [self findAsync:query];
}] continueWithSuccessBlock:^id(BFTask *task) {
NSArray *students = task.result;
PFObject *salutatorian = [students objectAtIndex:1];
[salutatorian setObject:@YES forKey:@"salutatorian"];
return [self saveAsync:salutatorian];
}] continueWithSuccessBlock:^id(BFTask *task) {
// Everything is done!
return nil;
}];

とこの用にせっかくPromiseですっきり書きたいのに[]が一杯でダサい地獄に陥ることもありません。

以前からPromise/Defferdはいくらか存在していたのですが、この辺の記述になってしまうライブラリばかりだったのですが、PromiseKitはその点をクリアにしているため、ようやく使いたいライブラリが出たという感じです。

Swiftの登場でこの辺りの問題もそもそもなくなるかもしれませんが、PromiseKitはSwiftの対応も進めているので今から導入しておいても損はないでしょう。


PromiseKitでアニメーションを記述する

個人的に最近一番困っていたのは複雑なアニメーションをいろんな箇所で多用するのですが、アニメーション自体も複数組み合わせて記述する場合

例えば、Viewをバウンスさせるようなアニメーションを記述したい場合,

UIView animateKeyframesWithDuration実装使用と思うと

 self.target.transform = CGAffineTransformMakeTranslation(0, -300);

[UIView promiseWithDuration:self.duration/4 delay:self.delay options:0 animations:^{
// End
self.target.transform = CGAffineTransformMakeTranslation(0, -10);
} completion:^(BOOL finished) {
[UIView promiseWithDuration:self.duration/4 delay:0 options:0 animations:^{
// End
self.target.transform = CGAffineTransformMakeTranslation(0, 5);
} completion:^(BOOL finished) {
[UIView promiseWithDuration:self.duration/4 delay:0 options:0 animations:^{
// End
self.target.transform = CGAffineTransformMakeTranslation(0, -2);
} completion:^(BOOL finished) {
[UIView animateKeyframesWithDuration:self.duration/4 delay:0 options:0 animations:^{
// End
self.target.transform = CGAffineTransformMakeTranslation(0, 0);
} completion:^(BOOL finished) {
[self next];
}];
}];
}];
}];

大分インデントが凄いことになってしまいます。

これを改善したくてPromiseライブラリの決定版を待ち望んでましたが、PromiseKitの登場でようやく改善できます。

こちらアニメーション周りの実装がなかったため、私の方で実装して本体に取り込んでもらいました。

これでもりもり実装ができます。

この修正の入ったPromiseKitを使うと以下のように記述ができます。

 // 3つのラベルを画面外から順番に移動するアニメーション

self.label1.frame.origin.y = -100;
self.label2.frame.origin.y = -100;
self.label3.frame.origin.y = -100;

__weak typeof(self) weakSelf = self;
dispatch_promise_on(dispatch_get_main_queue(),^{
return [UIView promiseWithDuration:0.2
animations:^{
weakSelf.label1.frame.origin.y = 100;

}];
}).then(^(BOOL finished){
return [UIView promiseWithDuration:0.2 animations:^{
weakSelf.label3.frame.origin.y = 200;

}];
}).then(^(BOOL finished){
return [UIView promiseWithDuration:0.2 animations:^{
weakSelf.label3.frame.origin.y = 300;

}];
});

どうでしょう?インデントが恐ろしいことにならずに、UIAnimationを記述することができます。

UIView animationXXXX 系のメソッドをPromise化するメソッドを追加してますので、同じような使い方でPromise化できるはずです。

PromiseKitはこのように実装の拡張自体も簡単ですのでほかのライブラリもPromise化は容易です。

みなさんつかってみてください