あと数年したらUITableViewが使えなくなる??
Xcode12から登場したUICollectionViewListCellですが、機能としてはUITableViewの変わりとなるような新しいUIパーツです。
UICollectionViewListCellでUITableViewライクな画面が作成することができるのです。
これってもう、UITableViewには用無しってことですよね...
ということで、今後リスト形式の画面を表示する時に使用するであろうUICollectionViewListCell
を試しに使って見ようと思います。
1.レイアウトの作成(UICollectionViewLayout)
今回はinsetGrouped
形式のリストを表示したかったので以下のように設定しました。
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
}
これでヘルスケアのようなリスト形式的のレイアウト準備が出来ました。
2.表示データの取得
今回はQiitaの記事を取得するAPIを実行してタイトルを画面に表示します。
そのため、記事の一覧を取得処理を作成します。
Alamofireを使用します
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の構成を設定します。
今回はシンプルな1セクションにしたかったため以下のようなEnumを作成しました。
extension FirstViewController {
enum Section {
case main
}
}
つぎに表示の仕方を登録します。
TableViewでいうとUITableViewDataSourceに近い処理だと思います。
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)
}
ライフライフサイクルに合わせて作成したメソッドを実行する
最終的にはこのようなクラスになりました。
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]?
}
実行してみる
とりあえず使うことができた。
Swift久しぶりすぎてUICollectionViewDiffableDataSourceの使い方も忘れてしまっていました...
UICollectionViewListCell
だけでなくUICollectionView
自体がかなり進化してきているので、ついていけるようにがんばります!
次はリフレッシュ処理や追加読み込み処理なども追加して、アプリとして使えるレベルまで進めていこうと思います。