iOS14のUICollectionViewをCustomCellで実装する。

# はじめに

前回、[iOS14のUICollectionViewを実装する記事](https://qiita.com/maruiyugo/items/70b02bf22bec51f3b97a "Qiita")を書いたので、ついでにカスタムセルの扱いも変わったらしいしこれも記事に書こう！と思って書いているのですが今までとかなり扱いが変わっているので大変でした。
あってるか不安なのでここ間違ってるよ、こう書いた方がいいよ！とかあったらコメントください :persevere:




## カスタムセル登場人物　一言紹介コーナー

### UICollectionViewListCellクラス

ただのセルクラスです。
特に何もしません。

### UICellConfigurationStateクラス

状態を保持してくれます。
`isSelected`、`isFocused`がデフォルト用意されています。
カスタムして追加も可能です。
（今回は扱いません。）

### UIViewクラス

カスタムビューを作ります。

### UIContentConfigurationクラス

カスタムビューを初期化し、セルから表示するデータと`UICellConfigurationState`を受け取り、カスタムビューに反映させ、セルにビューを返すクラスです。
一番の大切です。4番です。

## 実装イメージ

前回の記事と違いを出すために星を入れたのが絶妙にださい。。

<img width="40%"  src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738919/dd7181aa-b3d5-5144-43e6-75317dc00065.png">

## 全コード

全部で6ファイルです。
`SimpleCustomCell.storyboard`
`SimpleCustomCellViewController.swift`
`SimpleCustomCell.swift`
`SimpleCustomCellConfiguration.swift`
`SimpleCustomCellView.xib`
`SimpleCustomCellView.swift`

## SimpleCustomCellViewController.swift

```swift:SimpleCustomCellViewController.swift
class SimpleCustomCellViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    enum Section: CaseIterable {
        case kanto
        case kyusyu
    }
    
    let data: [Section: [String]] = [
        .kanto: ["Kanto", "Tokyo", "Chiba"],
        .kyusyu: ["Kyusyu", "Fukuoka", "Miyazaki"]
    ]

    var collectionViewDataSource: UICollectionViewDiffableDataSource<Section, String>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var layoutConfig = UICollectionLayoutListConfiguration(appearance: .grouped)
        layoutConfig.headerMode = UICollectionLayoutListConfiguration.HeaderMode.firstItemInSection

        let layout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
        collectionView.collectionViewLayout = layout
        
        collectionViewDataSource = createDataSource()
        reloadList()
    }

    func createDataSource() -> UICollectionViewDiffableDataSource<Section, String> {
        
        let normalCell = UICollectionView.CellRegistration<UICollectionViewListCell, String> { (cell, indexPath, text) in
            var content = cell.defaultContentConfiguration()
            content.text = text
            cell.contentConfiguration = content
        }
        
        let customCell = UICollectionView.CellRegistration<SimpleCustomCell, String> { (cell, indexPath, text) in
            cell.title = text
        }
        
        return UICollectionViewDiffableDataSource<Section, String>(collectionView: collectionView) {
            (collectionView, indexPath, text) -> UICollectionViewCell? in
            
            // セクションタイトルには、通常のセルを使っています。
            if indexPath.row == 0 {
                return collectionView.dequeueConfiguredReusableCell(using: normalCell, for: indexPath, item: text)
            } else {
                return collectionView.dequeueConfiguredReusableCell(using: customCell, for: indexPath, item: text)
            }
        }
    }
    
    func reloadList(){
        var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
        snapshot.appendSections(Section.allCases)
        snapshot.appendItems(data[.kanto]!, toSection: .kanto)
        snapshot.appendItems(data[.kyusyu]!, toSection: .kyusyu)
        collectionViewDataSource.apply(snapshot)
    }
}
```

### かいせつ

特別なのはここぐらいです。
`UICollectionViewListCell`を設定するときは、`defaultContentConfiguration`メソッドを呼んでcontentを設定していましたが、その処理は`SimpleCustomCell`に移動しているためここでは`title`を渡すだけになっています。

```swift
let customCell = UICollectionView.CellRegistration<SimpleCustomCell, String> { (cell, indexPath, text) in
	cell.title = text
}
```

## SimpleCustomCell.swift

```swift:SimpleCustomCell.swift
class SimpleCustomCell: UICollectionViewListCell {

    var title: String!

    override func updateConfiguration(using state: UICellConfigurationState) {
        var newConfiguration = SimpleCustomCellConfiguration()
        newConfiguration.title = title
        contentConfiguration = newConfiguration
    }
}
```

### かいせつ

`updateConfiguration`は、collectionViewにデータが`apply`されたときと`setNeedsUpdateConfiguration`メソッドが呼ばれレイアウトの更新が必要になった時に呼ばれるメソッドです。
ここでは、Configurationを初期化し、データ(title)をつめてセル自身のcontentConfigurationに設定しています。
（引数のstateは今回状態を扱わないので未使用です。）

## SimpleCustomCellConfiguration.swift

```swift:SimpleCustomCellConfiguration.swift
struct SimpleCustomCellConfiguration: UIContentConfiguration, Hashable {

    var title: String?
    
    func makeContentView() -> UIView & UIContentView {
        return SimpleCustomCellView(configuration: self)
    }

    func updated(for state: UIConfigurationState) -> Self {
        return self
    }
}
```

### かいせつ

`makeContentView`メソッドは、セルに表示するビューを生成するメソッドです。
引数で自身をビューに渡しています。
`updated`メソッドは状態が変わったら呼ばれるメソッドです。未使用です。

## SimpleCustomCellView.xib(.swift)

<img width="100%"  src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738919/72aefdf5-f314-7fb7-4971-b36bf7fbb805.png">

```swift:SimpleCustomCellView.swift
class SimpleCustomCellView: UIView, UIContentView {
    
    @IBOutlet var nameLabel: UILabel!

    var configuration: UIContentConfiguration
    
    init(configuration: SimpleCustomCellConfiguration) {
        self.configuration = configuration
        super.init(frame: .zero)
        loadNib()
        setUpUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func loadNib() {
        let nib = UINib(nibName: "\(SimpleCustomCellView.self)", bundle: nil)
        guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else { return }
        view.frame = frame
        addSubview(view)
    }
    
    private func setUpUI() {
        guard let configuration = configuration as? SimpleCustomCellConfiguration else { return }
        nameLabel.text = configuration.title
    }
}
```

### かいせつ

xibをloadし、渡されたconfigurationのデータをLabelに設定しています。
特に解説するところはないですん。

## まとめ

`Configuration`を使用することによって軽量になり、再利用が簡単になるようです。
今までのDelegate、DataSourceを使った方法に比べたら、どこでどのメソッドが呼ばれるかわからずブラックボックス感が増えたなと感じてしまいました。
慣れたら気にならなくなるのでしょうか。。

次は今回使わなかったstateを使用して記事を書こうと思います。
はげみになるのでよかったらLGTMお願いします＼(^o^)／

gitはこちら！
https://github.com/ymarui/iOS14_UICollectionView_sample

## 参考文献
https://qiita.com/shiz/items/4227accc7d13ae439d1d#推奨される使用方法
https://swiftsenpai.com/development/collectionview-expandable-list-part2/