Xcode12から追加されたUICollectionViewListCellを使ってみた。

## あと数年したらUITableViewが使えなくなる？？

Xcode12から登場したUICollectionViewListCellですが、機能としてはUITableViewの変わりとなるような新しいUIパーツです。
UICollectionViewListCellでUITableViewライクな画面が作成することができるのです。

これってもう、UITableViewには用無しってことですよね...

ということで、今後リスト形式の画面を表示する時に使用するであろう`UICollectionViewListCell`を試しに使って見ようと思います。

## 1.レイアウトの作成（UICollectionViewLayout）
今回は`insetGrouped`形式のリストを表示したかったので以下のように設定しました。

```swift

override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.title = "タイトル"
    collectionView.collectionViewLayout = createLayout()
}

private func createLayout() -> UICollectionViewLayout {
    let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
    let layout = UICollectionViewCompositionalLayout.list(using: config)
    return layout
}
```

これでヘルスケアのようなリスト形式的のレイアウト準備が出来ました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470777/200c0e6d-739b-e895-75e2-f3ea5045f706.png" width=50%>

## 2.表示データの取得
今回はQiitaの記事を取得するAPIを実行してタイトルを画面に表示します。
そのため、記事の一覧を取得処理を作成します。
Alamofireを使用します

```swift
private func getData() {
    AF.request(
        "https://qiita.com/api/v2/items",
        method: .get
    ).responseJSON { response in
        switch response.result {
        case .success:
            if let data = response.data {
                let json = try! JSONDecoder().decode([ItemsResponse].self, from: data)
                print(json)
            }
        case let .failure(error):
            print(error)
        }
    }
}

struct ItemsResponse: Codable, Hashable {
    let body: String?
    let coediting: Bool?
    let commentsCount: Int?
    let createdAt: String?
    let group: String?
    let id: String?
    let likesCount: Int?
    let pageViewsCount: String?
    let privateField: Bool?
    let reactionsCount: Int?
    let renderedBody: String?
    let tags: [Tag]?
    let title: String?
    let updatedAt: String?
    let url: String?
    let user: User?
}
struct User: Codable, Hashable {
    let descriptionField: String?
    let facebookId: String?
    let followeesCount: Int?
    let followersCount: Int?
    let githubLoginName: String?
    let id: String?
    let itemsCount: Int?
    let linkedinId: String?
    let location: String?
    let name: String?
    let organization: String?
    let permanentId: Int?
    let profileImageUrl: String?
    let teamOnly: Bool?
    let twitterScreenName: String?
    let websiteUrl: String?
}

struct Tag: Codable, Hashable {
    let name: String?
    let versions: [String]?
}
```

これで作成したメソッドを実行するとレスポンスのjsonを取得することができます。

## 3. 表示の仕方を登録する
まずはCollectionViewのSectionの構成を設定します。
今回はシンプルな１セクションにしたかったため以下のようなEnumを作成しました。

```swift
extension FirstViewController {
    enum Section {
        case main
    }
}
```

つぎに表示の仕方を登録します。
TableViewでいうとUITableViewDataSourceに近い処理だと思います。

```swift
private func setupCollectionView() {
    let registration = UICollectionView.CellRegistration<UICollectionViewListCell, ItemsResponse> { cell, _, text in
        var content = cell.defaultContentConfiguration()
        // 記事のタイトルを設定
        content.text = text.title
        cell.contentConfiguration = content
    }
    dataSource =
        UICollectionViewDiffableDataSource<Section,
                                           ItemsResponse>(collectionView: self.collectionView) { collectionView, indexPath, text in
        collectionView
            .dequeueConfiguredReusableCell(using: registration,
                                           for: indexPath,
                                           item: text)
    }
}
```

## 画面の更新処理を作成
スナップショットを取得して、セクションとアイテムを追加する感じらしい...

```swift
private func populate(with response: [ItemsResponse]) {
    var snapshot = NSDiffableDataSourceSnapshot<Section, ItemsResponse>()
    snapshot.appendSections([.main])
    snapshot.appendItems(response)
    dataSource?.apply(snapshot)
}
```

## ライフライフサイクルに合わせて作成したメソッドを実行する
最終的にはこのようなクラスになりました。

```swift
import Alamofire
import UIKit

class FirstViewController: UIViewController {

    @IBOutlet private weak var collectionView: UICollectionView!

    private var dataSource: UICollectionViewDiffableDataSource<Section, ItemsResponse>?
    private var displayData: [ItemsResponse] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "タイトル"
        collectionView.collectionViewLayout = createLayout()
        setupCollectionView()
        getData()
    }

    private func createLayout() -> UICollectionViewLayout {
        let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
        let layout = UICollectionViewCompositionalLayout.list(using: config)
        return layout
    }

    private func setupCollectionView() {
        let registration = UICollectionView.CellRegistration<UICollectionViewListCell, ItemsResponse> { cell, _, text in
            var content = cell.defaultContentConfiguration()
            content.text = text.title
            cell.contentConfiguration = content
        }

        dataSource =
            UICollectionViewDiffableDataSource<Section,
                                               ItemsResponse>(collectionView: self.collectionView) { collectionView, indexPath, text in
            collectionView
                .dequeueConfiguredReusableCell(using: registration,
                                               for: indexPath,
                                               item: text)
        }
    }

    private func populate(with response: [ItemsResponse]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, ItemsResponse>()
        snapshot.appendSections([.main])
        snapshot.appendItems(response)
        dataSource?.apply(snapshot)
    }

    private func getData() {
        AF.request(
            "https://qiita.com/api/v2/items",
            method: .get
        ).responseJSON { response in
            switch response.result {
            case .success:
                if let data = response.data {
                    let json = try! JSONDecoder().decode([ItemsResponse].self, from: data)
                    self.populate(with: json)
                }
            case let .failure(error):
                print(error)
            }
        }
    }

}

extension FirstViewController {
    enum Section {
        case main
    }
}
/// Qiita記事マッピング用
struct ItemsResponse: Codable, Hashable {

    let body: String?
    let coediting: Bool?
    let commentsCount: Int?
    let createdAt: String?
    let group: String?
    let id: String?
    let likesCount: Int?
    let pageViewsCount: String?
    let privateField: Bool?
    let reactionsCount: Int?
    let renderedBody: String?
    let tags: [Tag]?
    let title: String?
    let updatedAt: String?
    let url: String?
    let user: User?

}
struct User: Codable, Hashable {

    let descriptionField: String?
    let facebookId: String?
    let followeesCount: Int?
    let followersCount: Int?
    let githubLoginName: String?
    let id: String?
    let itemsCount: Int?
    let linkedinId: String?
    let location: String?
    let name: String?
    let organization: String?
    let permanentId: Int?
    let profileImageUrl: String?
    let teamOnly: Bool?
    let twitterScreenName: String?
    let websiteUrl: String?
}

struct Tag: Codable, Hashable {
    let name: String?
    let versions: [String]?
}
```


## 実行してみる
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470777/92af153f-7e33-e637-8b82-1656887ff07d.png" width=50%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470777/f731babb-03c3-95f0-7e4e-79edb7bb60d3.png" width=50%>

## とりあえず使うことができた。
Swift久しぶりすぎてUICollectionViewDiffableDataSourceの使い方も忘れてしまっていました...
`UICollectionViewListCell`だけでなく`UICollectionView`自体がかなり進化してきているので、ついていけるようにがんばります!

次はリフレッシュ処理や追加読み込み処理なども追加して、アプリとして使えるレベルまで進めていこうと思います。