UICollectionViewのlayoutSubviewをブロックで処理する + FizzBuzzサンプル

## UICollectionViewの拡張
UICollectionViewCellを使う場合、原則としてはサブクラスを作るのがオススメです。
で、それが分かった上で、それでも手を抜きたい場合にはこんなブロックがあると捗ります。

```objc:BTKCollectionViewCell.h
#import <UIKit/UIKit.h>

@interface BTKCollectionViewCell : UICollectionViewCell

@property(copy) void (^onLayoutSubview)(BTKCollectionViewCell *cell);

@end
```

```objc:BTKCollectionViewCell.m
#import "BTKCollectionViewCell.h"

@implementation BTKCollectionViewCell

- (void)layoutSubviews{
    [super layoutSubviews];
    if(self.onLayoutSubview){
        self.onLayoutSubview(self);
    }
}

@end
```
ブロックの引数にcell自身を入れているのは、間違えて循環参照してしまうのを避けるためです。


## Fizz Buzzサンプル
cellのコードだけだと便利さが伝わらないと思うので、fizz buzzしてみました。

###仕様

コンテンツ

* 数字は黒文字のUILabel
* Fizzは青文字のUILabel
* Buzzは赤文字のUILabel
* FizzBuzzは紫のUIButton

サイズ

* 数字は140 x 20
* Fizzは横倍
* Buzzは縦倍
* FizzBuzzは縦横倍

### コード

```objc:BTKViewController.h
#import <UIKit/UIKit.h>

@interface BTKViewController : UIViewController<UICollectionViewDataSource,UICollectionViewDelegateFlowLayout>

@end
```

```objc:BTKViewController.m
#import "BTKViewController.h"
#import "BTKCollectionViewCell.h"

typedef enum BTKViewControllerCellType : NSUInteger {
    BTKViewControllerCellTypeNum, // Default
    BTKViewControllerCellTypeFizz,
    BTKViewControllerCellTypeBuzz,
    BTKViewControllerCellTypeFizzBuzz,
    BTKViewControllerCellTypeEnd
} BTKViewControllerCellType;

@interface BTKViewController ()

@property UICollectionView *collectionView;

@end

@implementation BTKViewController

- (void)loadView
{
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc]init];
    
    self.collectionView = [[UICollectionView alloc]initWithFrame:CGRectZero
                                            collectionViewLayout:layout];
    self.collectionView.backgroundColor = [UIColor grayColor];
    self.collectionView.dataSource = self;
    self.collectionView.delegate = self;
    // Register cell for each type
    for(int i = 0 ; i < BTKViewControllerCellTypeEnd ; i++){
        [self.collectionView registerClass:[BTKCollectionViewCell class]
                forCellWithReuseIdentifier:[self reuseIdentifierForType:i]];
    }
    // Use CollectionView as root view
    self.view = self.collectionView;
}

- (BTKViewControllerCellType) typeForIndexPath : (NSIndexPath*)indexPath
{
    int count = indexPath.item + 1;
    BOOL fizz = count % 3 == 0;
    BOOL buzz = count % 5 == 0;
    if(fizz && buzz){
        return BTKViewControllerCellTypeFizzBuzz;
    }
    else if(fizz){
        return BTKViewControllerCellTypeFizz;
    }
    else if(buzz){
        return BTKViewControllerCellTypeBuzz;
    }
    else{
        return BTKViewControllerCellTypeNum;
    }
}

- (NSString*) reuseIdentifierForType : (BTKViewControllerCellType) type
{
    return [NSString stringWithFormat:@"Cell%d",type];
}

#pragma mark UICollectionViewDataSource

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}

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

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    BTKViewControllerCellType type = [self typeForIndexPath:indexPath];
    BTKCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[self reuseIdentifierForType:type]
                                                                            forIndexPath:indexPath];
    switch (type) {
        case BTKViewControllerCellTypeFizz:{
            UILabel *textLabel = (UILabel*)[cell viewWithTag:1];
            if(!textLabel){
                textLabel = [[UILabel alloc]init];
                textLabel.textAlignment = NSTextAlignmentCenter;
                textLabel.tag = 1;
                textLabel.backgroundColor = [UIColor whiteColor];
                textLabel.text = @"Fizz";
                textLabel.textColor = [UIColor blueColor];
                [cell addSubview:textLabel];
                [cell setNeedsLayout];
            }
            break;
        }
        case BTKViewControllerCellTypeBuzz:{
            UILabel *textLabel = (UILabel*)[cell viewWithTag:1];
            if(!textLabel){
                textLabel = [[UILabel alloc]init];
                textLabel.textAlignment = NSTextAlignmentCenter;
                textLabel.tag = 1;
                textLabel.backgroundColor = [UIColor whiteColor];
                textLabel.text = @"Buzz";
                textLabel.textColor = [UIColor redColor];
                [cell addSubview:textLabel];
                [cell setNeedsLayout];
            }
            break;
        }
        case BTKViewControllerCellTypeFizzBuzz:{
            // Fizz Buzz should be button
            UIButton *textButton = (UIButton*)[cell viewWithTag:1];
            if(!textButton){
                textButton = [UIButton buttonWithType:UIButtonTypeCustom];
                textButton.tag = 1;
                textButton.backgroundColor = [UIColor whiteColor];
                [textButton setTitle:@"Fizz Buzz" forState:UIControlStateNormal];
                [textButton setTitleColor:[UIColor purpleColor] forState:UIControlStateNormal];
                [textButton setTitleColor:[UIColor blackColor] forState:UIControlStateHighlighted];
                [cell addSubview:textButton];
                [cell setNeedsLayout];
            }
            break;
        }
        case BTKViewControllerCellTypeNum:
        default:{
            UILabel *textLabel = (UILabel*)[cell viewWithTag:1];
            if(!textLabel){
                textLabel = [[UILabel alloc]init];
                textLabel.textAlignment = NSTextAlignmentCenter;
                textLabel.tag = 1;
                textLabel.backgroundColor = [UIColor whiteColor];
                textLabel.textColor = [UIColor blackColor];
                [cell addSubview:textLabel];
                [cell setNeedsLayout];
            }
            // Num should be updated
            textLabel.text = [NSString stringWithFormat:@"%d",indexPath.item + 1];
            break;
        }
    }
    // view with tag 1 is the target view.
    // Resize target view to fill cell
    if(!cell.onLayoutSubview){
        cell.onLayoutSubview = ^(BTKCollectionViewCell *cell){
            UIView *view = [cell viewWithTag:1];
            view.frame = cell.bounds;
        };
    }
    return cell;
}

#pragma mark UICollectionViewDelegateFlowLayout

- (UIEdgeInsets) collectionView:(UICollectionView *)collectionView
                         layout:(UICollectionViewLayout *)collectionViewLayout
         insetForSectionAtIndex:(NSInteger)section
{
    return UIEdgeInsetsMake(10, 15, 10, 15);
}

- (CGFloat) collectionView:(UICollectionView *)collectionView
                    layout:(UICollectionViewLayout *)collectionViewLayout
minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
   return 10;
}

- (CGFloat) collectionView:(UICollectionView *)collectionView
                    layout:(UICollectionViewLayout *)collectionViewLayout
minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
   return 10;
}

- (CGSize) collectionView:(UICollectionView *)collectionView
                   layout:(UICollectionViewLayout *)collectionViewLayout
   sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    switch ([self typeForIndexPath:indexPath]) {
        case BTKViewControllerCellTypeFizz:
            return CGSizeMake(290, 20);
        case BTKViewControllerCellTypeBuzz:
            return CGSizeMake(140, 40);
        case BTKViewControllerCellTypeFizzBuzz:
            return CGSizeMake(290, 40);
        case BTKViewControllerCellTypeNum:
        default:
            return CGSizeMake(140, 20);
    }
}

@end
```
### 実行結果
![fizzBuzzCollectionView.png](https://qiita-image-store.s3.amazonaws.com/0/33513/e5c111c5-d8f7-3f5f-c729-adb714ce8fe9.png)

## まとめ

かなり手抜きの構造ですが、きっちりUICollectionViewCellの性能を引き出すようにデザインされています。

* 種別ごとにセルをわけて、再利用する
* レイアウト処理は必要な場合のみ行う

カスタムクラスを作った方が見通しがいい？いや、最初に書いたでしょ。それが王道。