UICollectionViewでお手軽にUITableViewライクなリストレイアウトを実現する「Lists in UICollectionView」セッションメモ #WWDC20

本記事ではWWDC20のセッション「Lists in UICollectionView」の概要を紹介します。
2020.06.26

はじめに

こんばんは。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」の概要を紹介しました。

次は、リストレイアウトに関するコードを実際に書いてみて、動作の詳細を把握していきたいと思います。

参考資料