UICollectionViewのセルの強調・選択時にセルの見た目を変化させる

UICollectionViewには、セルの強調・選択状態を管理する仕組みが備わっています。
UICollectionViewDelegateプロトコルに用意されているメソッドを実装することで、セルが強調、選択された時にセルの見た目を変化させることができます。

本記事ではこれらのメソッドを実装し、強調・選択状態に合わせてセルの背景を変化させる例をご紹介したいと思います。

なお、本記事では説明に必要な箇所のコードのみ掲載しています。
ベースとなる実装については以下の記事を参照してください。

[UICollectionViewを必要最低限のコードで実装してみる](https://qiita.com/takehilo/items/4af35623aadf3c59dc65)

## セルの強調時に背景色を変化させる
ベースの実装では以下のように背景色が赤のセルが並んでいます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/81715/34b1a1e8-3c0e-c3b2-6442-5adedca0eb1a.png)

これらのセルが強調状態になった時に、背景色が青に変化するようにしてみます。

はじめに、UICollectionViewDataSourceの`collectionView:cellForItemAt:`メソッドの実装を以下のように修正します。

```swift
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
    cell.backgroundColor = .red

    // 追加
    let selectedBGView = UIView(frame: cell.frame)
    selectedBGView.backgroundColor = .blue
    cell.selectedBackgroundView = selectedBGView

    return cell
}
```

セルの`selectedBackgroundView`プロパティにUIViewを設定しておくと、該当のセルが強調・選択状態になったときにそのビューに置き換わります。
ここでは背景色が青のビューを設定し、セルが強調・選択された時に背景色が変化するようにしています。

続いて、UICollectionViewDelegateプロトコルを実装します。
セルの強調・選択状態に関わるのは以下のメソッド群です。

```swift
extension ViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
        return false
    }

    func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool {
        return false
    }

    func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
        return true
    }

    func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
        print("Highlighted: \(indexPath)")
    }

    func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) {
        print("Unhighlighted: \(indexPath)")
    }

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print("Selected: \(indexPath)")
    }

    func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
        print("Deselected: \(indexPath)")
    }
}
```

最初の3つは、セルの強調・選択・選択解除を許可するかどうかを指定するメソッドです。

- `collectionView:shouldSelectItemAt:`
- `collectionView:shouldDeselectItemAt:`
- `collectionView:shouldHighlightItemAt:`

これらはデフォルトでtrueになるので、強調状態のみ有効にするためここでは`collectionView:shouldHighlightItemAt:`のみtrueを返しています。

後半の4つは、セルが強調・強調解除・選択・選択解除された時にセルをカスタマイズするためのメソッドです。
ここでは単にこれらのメソッドが呼ばれたかどうかを出力する処理を実装しています。

実行すると、以下のようにセルが強調(タップし続けている)状態のときだけ背景色が青に変わっています。

![highlight.gif](https://qiita-image-store.s3.amazonaws.com/0/81715/64a1fbb2-bf4c-3f3a-980b-5ad6911b7df4.gif)

## セルの選択時に背景色を変化させる
今度はセルが選択状態になった時に背景色が青に変化するようにしてみます。
以下の3つのメソッドはデフォルトでtrueなので、このケースでは実装しなくても良いです。

```swift
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
    return true  // 変更
}

func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool {
    return true  // 変更
}

func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
    return true
}
```

![select.gif](https://qiita-image-store.s3.amazonaws.com/0/81715/7da7a231-56ea-8dbb-f683-896117617707.gif)

セルをタップすると選択状態となり、背景色が青に変わります。
別のセルをタップすると、そのセルが選択状態になり、前のセルは選択状態が解除されて背景色が赤に戻ります。

## 強調状態を有効にしないと選択状態にならない？
以下のように強調状態は無効にして、選択状態のみ有効にしようとしたのですが、セルをタップしても選択状態になりませんでした。
選択状態のみ有効という指定はできないようです。

```swift
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
    return true
}

func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool {
    return true
}

func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
    return false
}
```

![highlight-off.gif](https://qiita-image-store.s3.amazonaws.com/0/81715/9eb057f6-86f3-2715-c3f2-23614127c76b.gif)

## 一度でも選択されたら背景色は青のままにする
ちょっとだけ応用。
セルが一度でも選択された背景色が青になったら、選択状態が解除されても青のままにするという実装をしてみます。

まず、UICollectionViewDataSourceの実装を元に戻します。

```swift
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
    cell.backgroundColor = .red
    return cell
}
```

次に、UICollectionViewDelegateを以下のように実装します。

```swift
extension ViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let cell = collectionView.cellForItem(at: indexPath)
        cell?.backgroundColor = .blue
    }
}
```

![blue.gif](https://qiita-image-store.s3.amazonaws.com/0/81715/ad8cba55-ae99-3dd9-a52a-7fe8281db428.gif)

別のセルをタップした時に前のセルの選択状態は解除されていますが、背景色は青のままです。
このように、UICollectionViewDelegateのメソッドを利用するとセルの状態に応じて好きなように見た目をカスタマイズすることができます。