HOME » Natsu note » iOS » [iOS6] Collection View 基本的な使い方

[iOS6] Collection View 基本的な使い方 2012/09/20/|iOS,

ついにiOS 6がリリースされましたね。iOS 6では、また数多くの機能が追加されました。ユーザー目線での新機能もさることながら、個人的には開発者にとって嬉しい新機能 Collection View に注目しています。

Collection Viewとは

Collection Viewとは、一言で言えば縦横方向にセルを並べることができるTable Viewのようなものです。UICollectionViewControllerを用いて画面を作成し、セルの中身は、UITableViewControllerと同様にDataSourceを使って指定します。

各セルのサイズは一定である必要はなく、また、セル間の最低間隔を指定しておくことで、レイアウトが自動で計算されキレイに配置されます。

かなり少ないコード量で、以下のスクリーンショットのようなUIを実現することが可能です。もちろん、デバイスの向きが変わればレイアウトも変わります(一行に表示するセルの数が変わる)。

iPhone スクリーンショット

CollectionView iPhone portrait

CollectionView iPhone landscape


iPad スクリーンショット

CollectionView iPad portrait

CollectionView iPad landscape


スクリーンショットを見て分かるように、デバイスの向きが変更されたときに一列に配置される写真の数が変わります。また、写真はそのとき適切な間隔をあけて配置されるというわけです。これは本当に便利。

さらにすごいことに、セルのレイアウトを自由自在に指定することもできるのです。これによって、例えばカバーフローのようなUIをごく少ないコード量で実現することも可能です。

サイズの設定

今回は、レイアウトのカスタマイズはせず、上のスクリーンショットのような画面を作成してみました。IBを使うと、記述しなくてはならないコードは本当に少なくて済みます。

CollectionView Size setup
まず、IBにてUICollectionViewのサイズ関連の設定を行います(iPhone用のnibファイルで説明しますが、iPadでも基本的に同様です)。スクロールの方向はデフォルトのまま縦スクロールとしました。

IBを用いると、各値の設定が簡単に行えます。各値がどのサイズを示しているかは、図中に示したとおりです。


各値は、それぞれ対応するUICollectionViewFlowLayoutのプロパティやUICollectionViewDelegateFlowLayoutのデリゲートメソッド(可変にする場合はこちらを利用)で設定することも可能です(表参照)。

指定サイズ property delegate
Cell Size itemSize collectionView:layout:sizeForItemAtIndexPath:
Header Size headerReferenceSize collectionView:layout:referenceSizeForHeaderInSection:
Footer Size footerReferenceSize collectionView:layout:referenceSizeForFooter:inSection:
Min Spacing (For Cells) minimumInteritemSpacing collectionView:layout:minimumInteritemSpacingForSectionAtIndex:
Min Spacing (For Lines) minimumLineSpacing collectionView:layout:minimumLineSpacingForSectionAtIndex:
Section Insets sectionInset collectionView:layout:insetForSectionAtIndex:

なお、ヘッダー/フッターのサイズは、スクロール方向に応じて横幅(または高さ)が自動で調整されます。縦スクロールの場合、設定したヘッダー/フッターサイズのうち高さが利用され、横幅はセクション幅に等しくなります。逆に、横スクロールの場合は高さがセクションの高さに一致します。

セルの用意

次に、セルを作成します。ここでは、contentViewの上に一枚だけUIImageViewがあるようなセルを作成しました。セルの初期化はinitWithFrame:で行います。

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        self.backgroundColor = [UIColor whiteColor];
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:
                                  CGRectMake(3, 3, frame.size.width - 6, frame.size.height - 6)];
                                // 枠をつけるためにimageViewのサイズを一回り小さくしておく
        
        // 可変サイズのセルに追従するようにautoresizingMaskを設定
        imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        
        [self.contentView addSubview:imageView];
        self.imageView = imageView;
    }
    return self;
}

なお、セルは再利用されますので、セルサイズが可変の場合、サブビューのframeがセルサイズに合わせて変化するようにautoresizingMaskまたはAuto Layoutの設定をしておく必要があります。

ヘッダーの用意

CollectionView header

ヘッダーを用意します。今度はコードではなくIBで作成しました。背景にUIImageViewを用い、その上にタイトルを表示するためのUILabelを配置します。

上でも書いたように、縦スクロールの場合、ヘッダー(フッター)の横幅がセクション幅まで引き延ばされます。このとき、UIImageViewやUILabelが一緒に引き延ばされてしまわないよう、autoresizingMaskまたはAuto Layoutの設定を適切にしておきましょう。ここでは、Auto Layoutを使いました。

Tips: Auto Layoutを無効にしてautoresizingMaskを利用する場合、File inspectorにある”Interface Builder Document”の設定で”Use Autolayout”のチェックをはずします。

セル/ヘッダー/フッターの登録

Table Viewと同様に、Collection Viewでもセル等を再利用します。そのために、まずはセル/ヘッダー/フッターのクラスまたはnibを登録する必要があります。これは、UICollectionViewControllerのサブクラスで行います。

セルの登録は、以下のメソッドで行います。

- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;

ヘッダー、フッターの登録は以下のメソッドで行います。

- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;

ここで、kindには、

UICollectionElementKindSectionHeader
UICollectionElementKindSectionFooter

のいずれかを指定します。

今回はセルはクラスを、ヘッダーはnibを指定します。

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // contentViewにcellのクラスを登録
    [self.collectionView registerClass:[CollectionCell class] forCellWithReuseIdentifier:@"MY_CELL"];
    
    // contentViewにheaderのnibを登録
    UINib *headerNib = [UINib nibWithNibName:@"HeaderView" bundle:nil];
    [self.collectionView registerNib:headerNib forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"MY_HEADER"];
}

データソース

まず、表示する写真とヘッダーのタイトルはそれぞれphotos, titlesプロパティに入っているものとします(photos, titlesはviewDidLoadで初期化しておきます)。

@interface ViewController ()
@property (nonatomic, strong) NSArray *titles;
@property (nonatomic, strong) NSArray *photos;
@end

いよいよデータソースの実装です。今回必要になるのは、

  • アイテム数の指定(必須)
  • セルの生成(必須)
  • セクション数の指定
  • ヘッダーの生成

の4つのメソッドです。

セクション数、アイテム数の指定

これはUITableViewDataSourceとそっくりです。

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return [[self.photos objectAtIndex:section] count];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return [self.photos count];
}

セルの生成

セルはTable Viewと同様に再利用します。セルのクラスまたはnibがすでに登録されているため、dequeueReusableCellWithReuseIdentifier:forIndexPath:はnilを返すことはありません。再利用可能なセルがない場合は、登録したクラスのinitWithFrameを呼んで(nibを登録した場合はnibをロードして)新規に生成したセルを返してくれます。

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

    CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MY_CELL" forIndexPath:indexPath];
    
    cell.imageView.image = [[self.photos objectAtIndex:indexPath.section] objectAtIndex:indexPath.item];
    return cell;
}

なお、もし再利用可能なセルがある場合、cellはprepareForReuseが呼ばれたあとの状態となります。必要に応じて、セルのクラス(UICollectionViewCellのサブクラス)で、prepareForReuseをオーバーライドしておきましょう。

ヘッダーの生成

セルと同様にヘッダーも生成します。ビューはdequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:で取得します(再利用可能なビューがない場合は新規に生成されます)。このとき、elementKindには、UICollectionElementKindSectionHeader(ヘッダー)またはUICollectionElementKindSectionFooter(フッター)を指定します。

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
  
    HeaderView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"MY_HEADER" forIndexPath:indexPath];
    
    headerView.label.text = [self.titles objectAtIndex:indexPath.section];
    return headerView;
}

可変セルサイズに対応

ここまででほぼ実装は完了です。しかし、このままではセルサイズが固定のままです(IBで指定した50×50のまま)。写真のサイズに応じてセルサイズを可変にしたいため、UICollectionViewDelegateFlowLayoutのメソッドを実装します。

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    UIImage *image = [[self.photos objectAtIndex:indexPath.section] objectAtIndex:indexPath.item];
    return CGSizeMake(image.size.width / 2, image.size.height / 2);
}

ここでは、便宜上、写真サイズの1/2をセルサイズにしていますが、特に意味はありません。好みのサイズに指定すればOKです。

まとめ

iOS 6から利用可能となったCollection Viewを使ってみました。基本的な使い方をするだけでも、かなりフレキシブルなUIが実現可能です。レイアウトをカスタマイズすれば凝ったUIも実現できます。いずれにせよ、コード量が少なくて済むところが魅力です。

質問、間違いの指摘などはツイッターでお願いします。@natsun_happy

ソースコード

GitHub

その他

サンプルで利用した写真は、
フリー写真素材 Futta.Net – 無料の風景フリー画像
のものを利用しました。

参考資料

「第3章 Collection View実践」としてより詳しい内容を書きました。チュートリアル形式で楽しめます。
内容の紹介はこちら。iPhoneアプリ開発エキスパートガイド iOS 6対応:執筆いたしました

iPhoneアプリ開発エキスパートガイド iOS 6対応
加藤 寛人 藤川 宏之 高丘 知央 西方 夏子 吉田 悠一 関川 雄介
インプレスジャパン
売り上げランキング: 4,856