話題のアプリ『Clear』のUIを独自実装してみた!(+Gumroad)


ひとつ前のエントリーの続きです!(動画も同じ)

今回こそ、当初の目的であった
『Clear for iPhone』のUIを独自に再現した実装
についての解説を書いていきますね。

初めに断っておきますが、今回のエントリーはStudy CoreDataシリーズのようなチュートリアルではありませんのであしからず。。
部分部分でどういった実装をすればあのUIが実現出来るのか?というポイントを解説していきます。

このエントリーの最後に、Clearと同じくなにかと話題のGumroadでテストアプリを公開していますので欲しい方は是非!
(※ 更新:内容が古くなったため公開は終了しました)
では、まず目次をば。

。。と、その前に前のエントリーで書きそびれてしまった事があったので。

ようこそタッチフリーの時代へ!画面に触れずに本が読める、MagicReaderを発表!
 – STUDIO LOUPE

こちら、僕がこのブログを始めた頃から目標にさせていただいているリオさんの新プロジェクト『GimmiQ』の近日リリース予定のiPadアプリです!
昨日の発表でかなり話題になってたのでご存知の人も多いかもですね。

なぜ、このMagicReaderを紹介したかと言うと、ツイキャスされていたアプリプロモーションの中でさらっと語られていた以下の内容が…▼

どんなに素早くめくろうとしても、指の動きに確実についてきてくれます!本物の本をパラパラめくるような感覚です!これまでの電子書籍は紙をめくるアニメーションなど、演出的な部分で本に似せようとしてきましたが、それによって本を再現できているとは思えませんでしたし、快適とも感じられませんでした。ページをパラパラめくって読みたいページを素早く見つけられる検索性こそが本特有の快適さだという想いから、このような凄まじく早いPDFリーダーが生まれたわけです。

これって先のエントリーで書いた『本質を見据えて作られたアプリ』と重なると思いませんか?

実際に僕もPDFリーダーのめくりアニメーションはもう必要ないとずっと思っていました。黎明期のUIとしては注目度と驚きがあり実際の本から電子書籍に移行しやすくさせるための技術としてベターではあったと思いますが、今では特に売りになるほどの要素でもないですし、実際、本の見た目をマネしたものであって本の操作性を実現するものにはなり得なかったワケです。

それを独自に解釈し直したアプリがClearとほぼ同時期に発表された事になんだかワクワクしてしまいました!(ちなみにiPhoneでiBooksのめくりアニメの礎となったClassicsというアプリのUIを作ったのとClearのUIデザイナーは同一人物なのだそう)

ってなわけで前置きが長くなってしまいましたが、今度こそもくじ!行きますっ!

1. データ構造とViewの基本構造
2. UITableViewControllerじゃないの !?
3. [self push/popViewController: …]なんかいらない!
4. CellとViewControllerの役割
5. Unfoldアニメーションの実装
6. もっとも手こずったこと。
 

1. データ構造とViewの基本構造

先に上げた動画を見て、勘の良い方ならもうお気づきかもですが、今回のテストプロジェクト(Re:Clear)にはデータベースは入れていません。
理由はUIを再現するのが目的だったのと、データ構造自体は他のアプリに比べて特別なものではないだろうと思えたからです。

ちなみにRe:Clearでは、データの断層を表現するためにNSArrayを使用しています。編集機能までは実装していませんが。

具体的には、以下のような単純な構造かと。

* Item    (属性にタイトルを含む)
* Stack (属性にタイトルと複数のItemを含んだセット)

これだけ。
(Clearでは3断層ありますが、最上層のStackは複数のStackを属性に持つStackという考え方であれば上記の2つで足ります。)
それらが『Stack1つに対してItem複数』という1対多のシンプルな関係でつながっています。あとは、それぞれ並び順を記憶するためのindex的な要素が必要なくらいでしょうか?

お次はView構造ですが、こちらもシンプル。
一種類のCellをItemとStackが併用して、あとはScrollViewを管理するViewControllerがひとつあればOK!

ねっ!爆裂シンプル!!
TopViewControllerとかDetailViewControllerとかいらないです。
データの断層を移動しても画面遷移はしないので 。

2. UITableViewControllerじゃないの !?

これは恐らくですが、TableViewではないと思われます。
理由はCellの折り紙(Unfold/Fold)アニメーションなどやCell間を広げたりする実装をTableViewで実現しようとするとかなり複雑になってしまうからです。

その代わりに、UIScrollViewをベースにしてCellを配置させる疑似TableViewを実装しています。(ただ今回は非表示になったセルをリユースする実装にはしていませんが、その辺はWWDCのビデオに解説がありますので見てみてくださいね。)
そうすることで、新規セルの挿入時などにcontentOffsetや各セルの位置を比較的簡単に調整してアニメーションさせる事が出来るようになります。

3. [self push/popViewController: …]なんかいらない!

ま、ViewControllerはひとつなので当然ですね(^〜^;)
では、どうやって断層移動して遷移させるのでしょう?

まず、Pushは単純です。

1. タップされたセルがStackなら自分が持っているItemをすべてセルの下にInsert。
2. タップされたStackから上のセル(同じ断層のStack)を上方向へ下のStackを下方向へスライドしつつフェードアウト。
3. 2と同時にItemのセルを広げて配置。

で、Pop時は。。

1. ピンチインが開始されたらひとつ上の断層のStackをすべて取得してAdd。
2. それをPush時と同様に並んだItemの上下に配置。
3. UIPinchGestureRecのscaleプロパティを元に、中心に向かって進む値を計算してItemとStackの位置を変更しつつ、唯一Itemと重なっている親のStackをフェードイン。
4. 指が離れたタイミングでStack同士がぴったり合っているならItemをすべてリムーブして、Stackを本来の位置にスライドさせる。

こちらは軽く読んだだけではちょっと分かりづらいかもしれませんが、やってる事自体はそれほど難しくありません。

要は断層移動の度に、必要なセルをAdd、不必要なセルをリムーブして画面遷移と同じ役割を果たすようにしているってわけ。
ここら辺で、一度セルとViewControllerの役割を整理しておきましょうか。

4. CellとViewControllerの役割

 

まず、親であるViewControllerは各セルのdelegateになっています。
Cellの役割はタップされた時に反応することと、スワイプされた時に自分自身をスライドさせる事です。
タップやスライドされた後で自分で自分を削除するのはおかしいので、削除するかどうかの判定も含めてdelegateに処理してもらいます。

次にViewControllerですが、こちらは前述のdelegateとしての役割の他に2種類のGestureRecognizer(※以下GRと略)を持っていますが、ScrollView自体もPinchとPanGRを持っているので正確には4つです。(ScrollViewのPinchGRは無効にしています。)

そしてTapGRはセルのタップではなく、セル外のタップによる新規セルの挿入処理を担当。(セルが無い下の黒い部分をタップした時のヤツ) PinchGRは、ピンチインによる断層移動とピンチアウトによる中央部分への新規セルの挿入処理を実行します。

もうひとつの新規セル挿入処理である下方向へのドラッグに対応するのは、ScrollViewのデリゲートメソッド群([scrollViewDidScroll:]など)です。

5. Unfoldアニメーションの実装

いよいよ皆さまお待ちかねのUnfoldアニメーションの実装ですよ〜〜!!

これは新規セルの挿入時に表示されるヤツですね。
iPad版公式Twitterアプリにムダに(…ゴメンナサイ^^;)隠しアクションとして実装されていたり、同じくiPadのPhoto.appのスライドショーのエフェクト(折り紙)で使われていたりします。(Photo.appのは凄い!)

で、今回使うのは2パターンで
トップとボトムへの挿入時のアニメーションと
セルとセルの間でのアニメーションですね。
 実際のところ、この2種類のアニメーション自体はそれほど難しくはないです。
CoreAnimationで実現しているのですが、まず、上下に入れる場合は新規セルのレイヤー(_newCell.layer)のアンカーポイントを 上、または下に変更してパースを付けて回転させるだけ。

例えばボトムへのインサートなら。。。

<br/>
// 新規セルを最下部のセルの下に挿入
SlidableCell *bottomCell = [[self cells] lastObject];
CGRect frame = CGRectMake(0.f, CGRectGetMaxY(bottomCell.frame),  _scrollView.bounds.size.width, CELL_HEIGHT);

// 新規セルをAdd
_newCell = [self insertCellWithFrame:frame insertPosition:InsertPositionBottom];

CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.f / - (1000.f * 0.2f);

// 新規セルのUnfoldアニメーション
__block UIView *__newCell = _newCell;
[UIView animateWithDuration:AUTO_DURATION delay:0.f
                    options:UIViewAnimationOptionCurveEaseOut
                 animations:^{

                     __newCell.layer.transform = CATransform3DRotate(transform, 0.f, 1.f, 0.f, 0.f);
                     __newCell.layer.position = cell.layer.position;
                     __newCell.layer.opacity = 1.f;
                 } completion:nil];

[self adjustContentSize];
<br/>

と、こんな感じです。(実際のコードとはちょっと違います)
ちなみに
_newCell = [self insertCellWithFrame:frame insertPosition:InsertPositionBottom];
のところでアンカーポイントの設定と配置などを処理していて、
[self adjustContentSize];
でCellが増えた場合のscrollView.contentSizeを変更させています。

トップへの挿入もほぼ同じですが、ドラッグされた分だけ回転させる仕様なのでアニメーションではないです。

ちょっと違うのはセル間への挿入で、画像の薄緑色の部分に当該セルの縦半分のサイズのCALayerを追加しています。
それを_newCellと反対の回転をさせる事で折り畳まれているように見せています。

それと細かいですが、上下の挿入の場合は背景が黒なのでセル自体の透明度を変更する事で影が濃くなったり薄くなったりしているように見えるのですが、セル間への挿入の場合、セルは同じ仕様でいいとしてもunfold専用のレイヤーを透過させてしまうと下のセルが透けて見えてしまうので、このレイヤーの上にさらに黒いレイヤーを乗せてそれを影として透過させています。

ちなみにUnfoldLayer内の文字や色などは、元のセルをキャプチャし画像にしたものを乗せているだけです。(まぁ、セル構造は単純なので元のセルと同じセルをUIViewで用意する形でも良いかもです。)

6. もっとも手こずったこと。

。。。と、ざぁーっと言葉で説明してきましたがCoreAnimationがまだ未経験の方などには、なにがなんだか。。。???って感じかもしれませんね。

でも、上下のセル挿入時のアニメーション辺りは比較的すんなり出来るかと思います。
が!一番面倒なのが、実はセルごとの配置処理だったりします。

なんせ下がScrollViewなので、なにも考えずに実装するとピンチ処理をしている間にも [scrollViewDidScroll:]なんかが呼ばれてしまって、そのメソッド内に書いた処理なども実行されてしまってワケワカメカメな状態になったりします。
Scrollが発生すると当然cellの位置なども変わってしまうのでそこら辺の制御が大変。。。

まぁ、ここまで書いておいてなんですが、実際にコードを見てもらった方が早いかもしれません(^〜^;)

こちらも流行にのって『Gumroad』で公開してみましたので
『人のまねで金をとるとは何事だ!!』という意見もおありかと思いますが、独自で調べて実装するだけの時間を買う感覚で見てもらえると嬉しいです!
(今回上手く行けば次回以降も実践アプリアイコンチュートリアルなんかもできるかもなぁ?などと考えたりもしています。)

以下がGumroadにアップしたRe:ClearのXcodeプロジェクトへのリンク($5)です
– https://gumroad.com/l/YWP

(※ 更新:内容が古くなったため公開は終了しました)

ではでは、ご期待に添えたかどうか分かりませんが今回のエントリーは以上です。
長文に関わらず読んでいただきありがとうございます!

今後ともよろしくですっ(`・ω・́)ゝ

広告

話題のアプリ『Clear』のUIを独自実装してみた!(+Gumroad)」への7件のフィードバック

  1. ピンバック: 話題のアプリ『Clear』のUIを独自実装してみた!(+Gumroad) | Everything was born from Love | WordBuff.in

  2. ピンバック: iPhoneアプリ、Clearの独自実装コードがGumroadで販売される | fladdict

  3. ピンバック: GumroadでiPhoneアプリ「Clear」のUIを実装したコード販売中 | ソフトアンテナブログ

  4. ピンバック: [夕刊] 明日 AppBank Store 1周年!記念イベントが起きるぞ! - AppBank

  5. ピンバック: 2012年2月21日の気になる - afforplus

  6. ピンバック: [夕刊] 明日 AppBank Store 1周年!記念イベントが起きるぞ! | iPhone 無料ゲーム情報

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中