UICollectionViewでお手軽にUITableViewライクなリストレイアウトを実現する「Lists in UICollectionView」セッションメモ #WWDC20
はじめに
こんばんは。CX事業本部の平屋です。
UICollectionView
でお手軽にUITableView
ライクなリストレイアウトを実現する仕組みが、iOS 14 で追加されました。その仕組みに関するセッション「Lists in UICollectionView」を視聴したので、本記事ではその概要を紹介します。
このセッションの動画は一般に公開されています。以下のページで確認できます。
また、記事中の画像はWWDCのセッションのスライドを引用しています。
本記事は Apple からベータ版として公開されているドキュメントを情報源としています。 そのため、正式版と異なる情報になる可能性があります。ご留意の上、お読みください。
iOS 14で追加された、UICollectionViewにおけるUITableViewライクなリストレイアウト を、本記事ではリストレイアウトと呼ぶことにします。
リストレイアウトを使用すれば、以下のようなUITableViewライクなリスト画面を実装できます。
Lists in UICollectionView
リストレイアウトの特徴として以下が紹介されています。
UITableView
ライクな外観にできる- iOS 13で追加されたCompositional Layoutがベース
- カスタマイズ性が高い
- 最適化されたself sizing
Compositional Layoutの詳細が知りたい方はWWDC 2019の以下のセッションを参照してとのことです。(私も最近見ました)
Components of a list
リストレイアウトを使用するにはUICollectionLayoutListConfiguration
クラスを使用します。これはNSCollectionLayoutSection
およびUICollectionViewCompositionalLayout
の上に構築されています。
List configuration
リストレイアウトに設定できる項目として以下の項目が紹介されています。
- Appearance
UITableView
と同様の以下のAppearanceを設定できる.plain
.grouped
.insetGrouped
- また、
UITableView
にはない以下のAppearanceも設定できる.sidebar
.sidebarPlain
- Separators
- 表示/非表示にするオプションがある
- Headers/Footers
- ヘッダーとフッターを構成するオプションがある
UITableView
と同様の項目が用意されていますね。
Simple setup
リストレイアウトを実現するシンプルな実装として以下の実装が紹介されています。UICollectionLayoutListConfiguration
を作成して外観を与え、この構成を使用してUICollectionViewCompositionalLayout
を作成します。そして、このレイアウトをUICollectionView
にセットします。
let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) // .insetGroupedスタイルを使用 let layout = UICollectionViewCompositionalLayout.list(using: configuration)
Per section setup
また、セクションごとにレイアウトを変える実装として以下の実装が紹介されています。sectionProviderと呼ばれるクロージャを受け取るタイプのイニシャライザでUICollectionViewCompositionalLayout
をイニシャライズします。クロージャの中でセクションごとのレイアウトを提供します。
let layout = UICollectionViewCompositionalLayout() { [weak self] sectionIndex, layoutEnvironment in guard let self = self else { return nil } // @todo: add custom layout sections for various sections let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment) return section }
Headers and footers
UITableView
のヘッダーとは少し異なり、UICollectionView
のヘッダーとフッターは明示的に有効にする必要があります。
ヘッダーとフッターの提供方法は2種類あります
方法1
ヘッダーとフッターをsupplementary viewとしてdataSourceに登録します。
// Header mode supplementary var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) configuration.headerMode = .supplementary // ヘッダーが不要の場合は.noneを指定する let layout = UICollectionViewCompositionalLayout.list(using: configuration) dataSource.supplementaryViewProvider = { (collectionView, elementKind, indexPath) in if elementKind == UICollectionView.elementKindSectionHeader { return collectionView.dequeueConfiguredReusableSupplementary(using: header, for: indexPath) } else { return nil } }
方法2
UICollectionView
のdelegateに同等のメソッドを実装します。
UICollectionViewListCell
今回追加されたリストレイアウト用のクラスで、UICollectionViewCell
のサブクラスです。
Separators
ラベルなどメインコンポーネントをセルに配置し、Separator Layout Guideを使えば、Separatorのインセットをメインコンポーネントにあわせることができるそうです。
Swipe actions
Swipe actionsを設定する実装として以下の実装が紹介されています。cellのleadingSwipeActionsConfiguration
に作成したアクションを設定します。
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Model> { (cell, indexPath, item) in // @todo configure the cell's content let markFavorite = UIContextualAction(style: .normal, title: "Mark as Favorite") { [weak self] (_, _, completion) in guard let self = self else { return } // trigger the action with a reference to the model self.markItemAsFavorite(with: item.identifier) completion(true) } cell.leadingSwipeActionsConfiguration = UISwipeActionsConfiguration(actions: [markFavorite]) }
Accessories
UITableView
と比べると以下のメリットがあるそうです。
- たくさんの新しいタイプを使用できる
- Leadingとtrailingに設定できる
- 複数のアクセサリを設定できる
UITableView
と同様のアクセサリーも使用できるそうです。
- Reorder
- セルにこのaccessoryを設定すると、UI上は並び替えできるようになる(並び替えが発生した時のハンドリングの実装は別途必要)
- Delete
- タップすると、設定済みのアクションが使われる
- Outline disclosure
- タップで子のセルを展開させる等のケースで使う
- section snapshot APIの実装が必要(Advances in diffable data sourcesのセッションで詳細を学べる)
実装
UICellAccessories
の配列をcellのaccessories
にセットします。disclosureIndicatorアクセサリは常にtrailingに表示され、Deleteアクセサリはediting modeの場合にleadingに表示されるそうです。
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { (cell, indexPath, item) in // @todo configure the cell's content cell.accessories = [ .disclosureIndicator(), .delete() ] }
表示条件はカスタマイズもできます。例えば以下はEditingではない場合にdisclosureIndicatorが表示されるようです。
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { (cell, indexPath, item) in // @todo configure the cell's content cell.accessories = [ .disclosureIndicator(displayed: .whenNotEditing), .delete() ] }
まとめ
このセッションのまとめとして以下が挙げられていました。
- 宣言的なAPIを使ってリストを実装でき、状態の管理などはUIKitが面倒を見てくれる
- UICollectionViewのListsレイアウトはカスタマイズ性が高い
- モジュール化されていて柔軟性が高い
- 使い方を簡単に習得できる
さいごに
セッション「Lists in UICollectionView」の概要を紹介しました。
次は、リストレイアウトに関するコードを実際に書いてみて、動作の詳細を把握していきたいと思います。