タイムラインの実装に欠かせない!UICollectionViewで作るFacebook風レイアウト

こんにちは!
なんと早くも2度目の登場です、iOSエンジニアの木村です。
最近、ついに実家から独立しました。
これで電車を気にせず、存分にObjective-Cを書けるかと思うと、もう(…)

と言いつつ、ネイティブチームの中で誰よりも早く帰るのですが w

さて、今回は今、僕が携わっている「Couples」 で実際に使っているViewの実装について、じっくり書きたいと思います!

FacebookのタイムラインのようなUIを実装したいと考えている方は必見です!

FacebookタイムラインのUIを再現!

blogmain_18_2

 

 Facebookを筆頭に、タイムラインを使うアプリでよく見かける、このUI。「Couples」でもタイムラインの写真の表示に使っています。
写真が横に並び、両端に前後の写真が少し見えています。ユーザーが何も考えなくても自然にスワイプしたくなる、さりげない心遣いが魅力です。実装目線で細かく見ていくと、、、スワイプした後、常に写真が中心で止まるように設計されています。
こういう細かい点を押さえつつ、再現スタートです。

作り方

実装方法として、UIScrollViewとUICollectionViewの2つの方法がありますが、今回はUICollectionViewを使います。

UIScrollViewでの実装は、表示するところまでは簡単です。しかしUIScrollViewにはViewの再利用に不向きで、バグの原因にもなる、という弱点があります。

沢山の写真を表示するために、Viewの再利用が必要になってくるため、少し難しいですがUICollectionViewで実装していきます。

早速ハマる

まずは、UICollectionViewのページングを利用し、普通にマージンを取って実装をスタート。一見、あっさりと実装できたかに見えたのですが……

 

blogmain_18_3

 

写真をスワイプするごとに、写真の位置がズレてしまう…という問題が発生。

 

UICollectionViewのページングはUICollectionViewのサイズ分動くので、ズレが起きないようにするためには、UICollectionViewのサイズとセルのサイズを同じにする必要があります。しかし、サイズを同じにしてしまうとClip subviews = NOにしても両サイドのセルが隠れてしまうのです。

 

ここで早速ハマってしまいました。
ただ、この問題は、ちょっとしたことで解決できたのです。

簡単3ステップで実装

1, まずViewControllerにUICollectionViewを配置します。

blogmain_18_image_2

 

  • Paging Enabled = YES
  • Scroll Direction = Horizontal
  • Clip Subviews = NO

 

レイアウトのサイズ、マージンはこのようにします。

 

blogmain_18_image_3

2, 次にセルの作成です

セルはxibファイルを用いて作成します。

 

UICollectionViewCellを配置、その上にUIImageViewを表示したい写真のサイズで配置します。

この時のポイントはUICollectionViewCellのサイズはUICollectionViewと同じにすることです。こうすることによってUICollectionViewのpagingによるズレが発生しなくなります。

 

デザイン上、写真の間に少しマージンが欲しいので

  • cellのサイズは(306,300)
  • imageViewのサイズは(300,300)

この設定でcellの中心に配置します。
これで写真の間に6pのマージンが出来ます。

 

blogmain_18_image_4

 

さらにimageViewにAutolayoutを設定します。

 

上の画像はAutolayoutを設定済みですが、
設定する場合、

blogmain_18_image_5_6

 

上図のように、サイズの固定とcellに対して中心になるように設定しましょう。

 

3, 次にUICollectionViewFlowLayoutのサブクラスを作ります。

先ほど説明したようにUICollectionViewとUICollectionViewCellのサイズを大きさを同じにすることで、pagingのズレはなくなります。しかしこのままでは、UICollectionViewのframeから外れたセルは、表示されずに消えてしまいます。

 

これはつまり、逆に言えばcellはUICollectionViewのframe内に1pでも入れば表示される、ということなのです!

 

では実際に解決していきましょう。
問題を解消するためにUICollectionViewFlowLayoutのサブクラスをつくります。

blogmain_18_image_7

 

作成すると.mファイルは以下のようになっていると思います。

 

Objective-C
#import "ERCollectionViewFlowLayout.h"
@implementation ERCollectionViewFlowLayout
@end

 

次にUICollectionViewLayoutが持っている、

 

 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;

 

上記のメソッドをオーバーライドします。

これは引数で渡されたrectの範囲内に表示されるCellのUICollectionViewLayoutAttributesの配列を返すメソッドです。

 

これを以下のようにオーバーライドしましょう。

 

Objective-C
#import "ERCollectionViewFlowLayout.h"

@implementation ERCollectionViewFlowLayout

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
    [attributes enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *attribute, NSUInteger idx, BOOL *stop) {
        CGRect rect = attribute.bounds;
        rect.size.width += 10;
        attribute.bounds = rect;
    }];
    return attributes;
}
@end

 

スーパークラスのUICollectionViewFlowLayoutが作ったCell同士が、ピッタリくっつくLayoutAttributesのframeを、両サイドに5p広げています。

 

これにより、cellとcellが重なり、両サイドのcellもUICollectionViewのframeに入るようになります。

(そのため、消えなくなります!)

 

blogmain_18_8

 

これで出来上がりです!

再利用もこのままで実装済みなため、たくさんの写真を横に並べることも可能です。

 

UIScrollViewのdelegateメソッドを使ってスクロールを制御することは可能ですが、Viewの再利用や、バグ予防の観点から、UICollectionViewが断然おススメです。

 

ちょっとしたオーバーライドでカスタムUIが実装出来てしまうので、UICollectionViewは便利ですね!

最後に

今回使ったUICollectionViewはデータを処理する部分とレイアウトを制御するクラスが分離しているので、カスタマイズ次第で、様々なUIを作ることができる魅力的なUIパーツです。またiOS7からは、UICollectionViewとアニメーションの相性を更に高めており、より美しいアニメーションを実装することが可能になっています。

アイデア次第では、もっと色々なUIが作れそうです!

マニアックなネタでしたが、いかがでしたでしょうか。
ちょっとでも興味がわいたという方は、ぜひエウレカに遊びに来てほしいです。

カスタムUIが大好きというiOSエンジニアの方、待っています。