この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 はじめに
UICollectionViewを使用すると、配列に格納されたデータを要素として、適切に改行やスペースを調整して並べてくれます。 今回は、このUICollectionViewを使用して、簡単な神経衰弱のようなアプリを作成してみました。
なお、UICollectionViewでは、レイアウトをサブクラス化して独自に設計できます。トランプが不揃いに並んでいる雰囲気は、この拡張を使用しています。
サンプルコード(http://github.com/furuya02/CardGameSample)
2 UICollectionView
プロジェクトの雛形として、UICollectionViewControllerがベースになっているものは無いので、Single View Applicationから作成し、最初のシーンを消しUICollectionViewControllerを置きました。
また、Refreshのメニューボタンを置くために、NavigationControllerをその前に置きました。
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からでも設定できます。
今回は、個々のカードを微妙に傾けたかったのですが、デフォルトのレイアウト指定では叶わないため、UICollectionViewFlowLayoutを継承した、独自のレイアウトクラスを設計しました。
CardLayout.h
#import <UIKit/UIKit.h>
@interface CardLayout : UICollectionViewFlowLayout
@end
設計したレイアウトクラスは、Storyboard上で、次のように指定しています。
レイアウトクラスでは、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/)