LoginSignup
101

More than 5 years have passed since last update.

UIViewControllerからUICollectionViewのDataSourceを分離し肥大化を防ぐ具体例

Last updated at Posted at 2014-07-10

ごめんなさい。この記事のように分離しても、CollectionViewを扱う複雑さは解消できないと思うようになり、最近はこのような分離する方法はとらなくなりました。分離しても最初だけスリムで結局肥大化してしまうので何も解消してはないというのが最近の気持ちです。

最近の気持ちは下記のスライドにまとめました。

記事: UICollectionViewDataSourceはViewControllerと別にして実装しないほうが良いと最近思う
https://speakerdeck.com/yimajo/uicollectionviewdatasourcehaviewcontrollertobie-nisiteshi-zhuang-sinaihougaliang-itozui-jin-si-u

とりあえず ↓の記事はそのままにしています

なぜUIViewControllerからDataSourceを分離する必要があるのか

iOSアプリ開発でよく使われるUIViewControllerはアプリの機能追加に伴い肥大化しメンテナンスコストが日々大きくなっていないでしょうか。

メンテナンスコストが大きくならないようにどのように設計すべきか、というのは「一つのクラスは一つの責任をもつ」というオブジェクト指向の原則に従うというシンプルな解答があります。

具体例としてiOS6以降から使えるようになったUICollectionViewを用いて説明したいと思います。

UICollectionViewに色付きの正方形を並べる具体例

完成形

まず完成形は次のようなものになります。UICollectionViewを使ってランダムに色が決まる正方形を並べていく例にしました。

スクリーンショット 2014-07-11 0.49.02.png

Storyboard

Storyboardを使って画面を作成します。UICollectionViewは使いますが、UICollectionViewControllerは使いません。

Storyboard.png

UICollectionViewcontrollerを使ってしまうと、UICollectionViewControllerDataSourceのプロトコルをViewControllerが実装しなければいけなくなるからなんですが、ここまでの説明ではいまいちピンとこないと思うので進めて読んでみて下さい。

ViewController

ViewControllerは次のように実装します。UICollectionViewを保持しており、そのdataSourceとしてオリジナルのクラスColorCollectionViewDataSourceを委譲します。

#import "ViewController.h"
#import "ColorCollectionViewDataSouce.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;

@property (strong, nonatomic) ColorCollectionViewDataSoruce *dataSouce;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];    

    self.dataSouce = [ColorCollectionViewDataSouce new];

    self.collectionView.dataSource = self.dataSouce;
}

DataSource

DataSourceはUICollectionViewに表示するデータを決定します。UICollectionViewDataSourceプロトコルを実装することが必須です。

#import "ColorCollectionViewDataSource.h"

@implementation ColorCollectionViewDataSource

#pragma mark collection view datasource

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 40;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell =[collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" 
                                                                          forIndexPath:indexPath];

    [self configureCell:cell];

    return cell;
}

#pragma mark -

- (void)configureCell:(UICollectionViewCell *)cell
{
    float hue = (rand() % 100) / 100.0f;

    cell.backgroundColor = [UIColor colorWithHue:hue
                                      saturation:1.0f
                                      brightness:1.0f
                                           alpha:1.0f];
    ;
}

@end

configureCell:メソッドによってランダムな色にして正方形のcellを40個返しています。

この例でのそれぞれの役割

ViewControllerの役割は

  • ViewControllerのライフサイクルについて実装している

ViewControllerの役割としてViewのイベントを処理するものがあります。ViewControllerはライフサイクルやタッチイベントに対する処理を受ける部分としての役割を担っていると考えると実装もシンプルになるかと思います。

ColorCollectionViewDataSourceの役割は

  • CollectionViewに表示するCellの数や色について実装している

場合によっては表示するCellの内容は通信やCoreDataを使って画像を表示したりするかもしれません。その場合はそれぞれの役割に応じた実装になるわけで、その実装についてViewControllerは知る必要はありません。

この例ではCellの色を変えるだけでしたが、私はCoreDataを使うことが多く、DataSourceはNSFetchedResultsControllerを保持してその内容をCellに表示するパターンがよくあります。NSFetchedResultsControllerはデータの変更に応じてデリゲートメソッドが実行されるため、それらの変更の種類に応じてDataSourceからUICollectionViewを保持するViewControllerに処理を委譲することもあります。

DataSourceにNSFetchedResultsControllerを使う場合のTips

DataSourceにNSFetchedResultsControllerDelegateを保持する場合、NSFetchedResultsControllerDelegateプロトコルを実装しデータの変化ごとにメソッドが動作するようにします。

//DataSourceで実装
- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    if (type == NSFetchedResultsChangeInsert) {
        [self.delegate dataSourceDidChangeInsertAtNewIndexPath:newIndexPath];
    }
}

その中で、FetchedResultsControllerDelegateというプロトコルを定義し、UICollectionViewを保持するViewControllerに処理を委譲することでViewへの操作を行うことができます。

@protocol FetchedResultsControllerDelegate <NSObject>

/**
 * CoreDataにデータが追加された場合に動作する
 *
 * @param  newIndexPath 新しく追加されたデータのIndexPath
 */
- (void)dataSouceDidChangeInsertAtNewIndexPath:(NSIndexPath *)newIndexPath;

@end
//ViewControllerで実装
- (void)dataSourceDidChangeInsertAtNewIndexPath:(NSIndexPath *)newIndexPath
{
    [self.collectionView insertItemsAtIndexPaths:@[newIndexPath]];
}

これによってViewControlllerはViewをコントロールするための必要最低限のコードで済むようになりました。

参考

objc.io #1-1 軽量なView Controller(日本語訳)
http://qiita.com/gonsee/items/76ee350d6ef266bb33da

UITableViewController を使わない設計
http://blog.fenrir-inc.com/jp/2010/10/tvc.html

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
101