[iOS] UICollectionViewで神経衰弱を作ってみた。

2016.02.10

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

UICollectionViewを使用すると、配列に格納されたデータを要素として、適切に改行やスペースを調整して並べてくれます。 今回は、このUICollectionViewを使用して、簡単な神経衰弱のようなアプリを作成してみました。

なお、UICollectionViewでは、レイアウトをサブクラス化して独自に設計できます。トランプが不揃いに並んでいる雰囲気は、この拡張を使用しています。

github サンプルコード(http://github.com/furuya02/CardGameSample)

2 UICollectionView

プロジェクトの雛形として、UICollectionViewControllerがベースになっているものは無いので、Single View Applicationから作成し、最初のシーンを消しUICollectionViewControllerを置きました。

また、Refreshのメニューボタンを置くために、NavigationControllerをその前に置きました。

002

ViewControllerの基底クラスは、UICollectionViewControllerに変更しています。

#import <UIKit/UIKit.h>

@interface ViewController : UICollectionViewController


@end

UICollectionViewControllerは、初めから、UICollectionViewのデリゲートになっており、セルは表示されませんが、このままでもエラー(例外)は発生しません。

3 データ(トランプ)

データとなるトランプのクラスは、次のように設計されています。 保持しているデータは、「数字」(一致を検出するため)や、「表向きかどうか」などです。

Card.h

@interface Card : NSObject

@property int no;
@property bool isFront;
@property int index;

-(id)initWithMark:(NSString*)mark no:(int)no;

- (NSString*) imageName;
- (void) Reverse:(UICollectionView *)collectionView;

@end

カードの画像(名)は、内部で保持しており、「表向きかどうか」で返す名前が変わります。 また、表裏を変更するメソッドReverse:を持っており、UICollectionViewのポインタを受け取ることで、当該セルの画像を変更しています。

Card.m

#import "Card.h"
#import "CardCell.h"

@interface Card ()
@property NSString *imageName;
@end

@implementation Card
-(id)initWithMark:(NSString*)mark no:(int)no {
    self = [super init];
    if (self != nil) {
        self.no = no;
        _imageName =  [NSString stringWithFormat:@"%@%2.2d",mark,no+1];
        _isFront = false;
    }
    return self;
}

// 画像ファイル名
- (NSString*) imageName {
    if ( _isFront ){
        return _imageName;
    }
    return @"z02";
}

// 表裏を変更する
- (void) Reverse:(UICollectionView *)collectionView {
    _isFront ^= 1;
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.index  inSection:0];
    CardCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
    [cell.image setImage:[UIImage imageNamed:self.imageName]];
}

@end

4 データソース

上記で設計したトランプクラスを配列の形で保持し、これをUICollectionViewのデータソースにしています。

<br />@property (nonatomic) NSMutableArray *cards;


// アイテム数を指定する(必須)
- (NSInteger) collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.cards.count;
}

//セルを返すメソッド(必須)
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    // 再利用キューからセルを取得
    CardCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CardCell" forIndexPath:indexPath];


    // セルの設定
    Card *card = self.cards[indexPath.item];

    [cell.image setImage:[UIImage imageNamed:card.imageName]];
    cell.delegate = self;
    return cell;
}

5 レイアウト

デフォルトのレイアウトでも、アイテムのサイズや、アイテム同士の間隔、表示のマージンなど細かい指定が可能です。

動的に変更したい場合は、それぞれで該当するデリゲートをトラップして変更することも可能です。 また、固定値でいいのであれば、StoryBoardからでも設定できます。

003

今回は、個々のカードを微妙に傾けたかったのですが、デフォルトのレイアウト指定では叶わないため、UICollectionViewFlowLayoutを継承した、独自のレイアウトクラスを設計しました。

CardLayout.h

#import <UIKit/UIKit.h>

@interface CardLayout : UICollectionViewFlowLayout

@end

設計したレイアウトクラスは、Storyboard上で、次のように指定しています。

004

レイアウトクラスでは、layoutAttributesForElementsInRectで全体のレイアウト属性を返しますので、ここからセルのレイアウトに関してだけを上書きしました。

CardLayout.m

#import "CardLayout.h"

@implementation CardLayout
// セルのレイアウト属性
- (void)setupItemAttributes:(UICollectionViewLayoutAttributes *) attributes {
    // 乱数で0〜30度程度傾けた
    int r = rand() % 4;
    CGFloat radian = M_PI / 180.0f * 10.0f * r;
    attributes.transform = CGAffineTransformMakeRotation(radian);
    attributes.zIndex = attributes.indexPath.item;
}

// 表示領域のレイアウト属性を返す
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {

    NSArray *allAttributes = [ super layoutAttributesForElementsInRect:rect];
    for (UICollectionViewLayoutAttributes * attributes in allAttributes){
        if (attributes.representedElementCategory == UICollectionElementCategoryCell){
            // セルのレイアウト属性
            [self setupItemAttributes:attributes];
        }
        // 今回は、補助ビューを使用していないので、その他は省略
    }
    return allAttributes;

}
@end

6 最後に

今回は、簡単なゲームのようなものを作成してみましたが、適度に調整して塩梅よく並べてくれるUICollectionViewは、結構、使い出があるような気がしてきました。 レイアウトをサブクラス化することで、細部まで調整が可能ですので、使い方によっては、面白いものが作れるかもしれません。

(トランプ画像の素材は、こちらのものを利用させて頂きました。http://sozai.7gates.net/docs/trump/