【Objective-C】UICollectionViewCellの選択表示

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

UICollectionViewCellで選択表示をさせようと思ってハマったのでメモ。

参考:Collection View プログラミングガイド(iOS用)【PDF】
※P25〜P28あたり。

選択状態をコードで制御するには・・・

たとえば、セルの選択状態をアプリケーション側で描画したい場合は、selectedBackgroundViewプロパティをnilにし、デリゲートオブジェクトに、外観を変更する処理を組み込んでください。
collectionView:didSelectItemAtIndexPath:メソッドに外観を変更する処理、
collectionView:didDeselectItemAtIndexPath:メソッドに元の状態に戻す処理を実装します。

ということらしいので、コードを書いてみた。

//UICollectionViewCell:編集
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell;
    
    cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell1" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];
    cell.selectedBackgroundView = nil;
    
    //(中略)

    return cell;
}

//
-(void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"選択した %@",indexPath);
    UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor redColor];
}

-(void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"選択解除した %@",indexPath);
    UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];
}

これだとタップしている間は、背景色が赤に変わるけれど、指を離すと白に戻ってしまう。

で、更に資料を読んでみると・・・

図 2-3に、未選択状態のセルにユーザがタッチしたときに発生する、一連の動作を示します。最初の「タッチダウン」イベントを受けて、コレクションビューはセルの「強調表示」状態をYESにします。もっとも、自動的にセルの外観に反映されるわけではありません。その後、「タッチアップ」イベントが発生すると、「強調表示」状態はNOに戻り、「選択」状態はYESに変わります。ユーザが選択状態を変更すると、コレクションビューはセルのselectedBackgroundViewプロパティに設定されているビューを表示します。コレクションビューが関与してセルの外観を変えるのは、この場合に限ります。ほかの視覚効果はデリゲートオブジェクトが行わなければなりません。

つまり、指を離すと選択状態にはなるものの didUnhighlightItemAtIndexPathが呼ばれて、表示が戻ってしまう・・・ということらしい。

結局、didHighlightItemAtIndexPathとdidUnhighlightItemAtIndexPathは使わず、以下の様に書いて解決。

//UICollectionViewCell:編集
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell;
    
    cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell1" forIndexPath:indexPath];
    
    //通常の背景
    UIView* backgroundView = [[UIView alloc] initWithFrame:cell.bounds];
    cell.backgroundColor = [UIColor whiteColor];
    cell.backgroundView = backgroundView;
    
    //選択時の背景
    UIView* selectedBGView = [[UIView alloc] initWithFrame:cell.bounds];
    selectedBGView.backgroundColor = [UIColor redColor];
    cell.selectedBackgroundView = selectedBGView;
    
    //(中略)
    
    return cell;
}

最初のコードで、選択時のイベントハンドラ(didSelectItemAtIndexPath)で選択済表示に変えることもできるけど、それだと選択済項目の表示を戻してやる処理が必要になるので、単一選択ではこれが一番簡単かなと。