WWDC Quick Look 💓 By SwiftGGTeam
Lists in UICollectionView

Lists in UICollectionView

元の動画を見る

ハイライト

iOS 14 では UICollectionView が UICollectionLayoutListConfiguration で UITableView 風リストを作れ、UICollectionViewListCell で自适应高さ、区切り線、スワイプ操作、accessories を設定できます。


主要内容

多くの UIKit プロジェクトには厄介な境界があります。単純なリストは UITableView、複雑なレイアウトは UICollectionView。ページにリスト、横スクロール領域、グリッド、階層構造が同時に必要になると、開発者は2体系を行き来し、データソースとレイアウトコードが分断されます。

iOS 14 はリストを UICollectionView に入れました。UICollectionLayoutListConfiguration は plain、grouped、inset-grouped、iPad sidebar 向けの sidebar、sidebar plain 外観を提供します。Compositional Layout 上に構築されるため、同じ collection view に表風リスト、直交スクロール領域、カスタムグリッドを共存させられます。

リスト cell も再設計されました。UICollectionViewListCell はデフォルトの自适应高さ、区切り線 layout guide、leading/trailing swipe actions、宣言的 accessories をサポートします。講演では特に警告しています。swipe action は index path を捕まえないでください。上に挿入や削除があると、古い index path は別データを指す可能性があります。

これにより UICollectionView がリスト画面のデフォルト起点になります。まず元の table view 外観を置き換え、その後 section ごとに横領域、outline、iPad sidebar を足せます。


詳細

collection view 全体のリストレイアウトを作る

(03:47) 最も単純な入口は UICollectionLayoutListConfiguration を作り、UICollectionViewCompositionalLayout.list(using:) に渡すことです。全 section が同じリスト外観のページに適します。

// Simple setup

let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
let layout = UICollectionViewCompositionalLayout.list(using: configuration)

キーポイント:

  • UICollectionLayoutListConfiguration はレイアウト側でリストを作る新しい型です。
  • appearance: .insetGrouped は inset grouped table view 風の外観を生成します。
  • UICollectionViewCompositionalLayout.list(using:) は collection view 全体のレイアウトを直接生成します。

section ごとにリストとカスタムレイアウトを混在させる

(04:40) 一部の section だけがリストなら、Compositional Layout の section provider を使います。システムが section レイアウトを要求するたびに、リスト section かカスタム section を返せます。

// Per section setup

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
}

キーポイント:

  • section provider は sectionIndexlayoutEnvironment を受け取り、section ごとに異なるレイアウトを返せます。
  • NSCollectionLayoutSection.list(using:layoutEnvironment:) は現在の section のリストレイアウトだけを作ります。
  • カスタムグリッド、横スクロール領域、リストは同じ UICollectionViewCompositionalLayout に共存できます。

(05:49) リストの header と footer は明示的に有効化が必要です。headerMode または footerMode.supplementary にすると、collection view は data source か delegate に supplementary view を要求します。

var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
configuration.headerMode = .supplementary
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
    }
}

キーポイント:

  • .supplementary は header が supplementary view で提供されることを意味します。
  • supplementaryViewProviderUICollectionView.elementKindSectionHeader や footer の element kind を判定する必要があります。
  • transcript は明確に警告します。supplementary モード有効後、header/footer 要求時に nil を返すと assertion が発生します。

(07:07) 階層データには firstItemInSection がより適します。section の最初の item を header 外観に設定し、section snapshot と相性が良いです。

var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
configuration.headerMode = .firstItemInSection
let layout = UICollectionViewCompositionalLayout.list(using: configuration)

キーポイント:

  • firstItemInSection は header にのみ適用されます。
  • このモードでは data source の最初の item が header を表し、通常コンテンツではありません。
  • 講演は階層データ構造と新しい section snapshot API に使うことを勧めます。

cell に swipe actions を設定する

(11:40) list cell では swipe actions は cell の能力です。コンテンツ設定と同じ場所で leadingSwipeActionsConfiguration や trailing 設定を行います。

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])
}

キーポイント:

  • swipe action は list configuration で作られた section に cell が描画されるときだけサポートされます。
  • action handler は indexPath ではなく item.identifier を捕まえます。
  • diffable data source の安定 item identifier と iOS 14 の cell registration と相性が良い書き方です。

accessories で cell 機能を宣言する

(14:55) UICollectionViewListCell の accessories は leading または trailing に設定でき、同じ側に複数置けます。一部の accessory は動作を起こします。delete accessory は trailing swipe actions を表示します。

let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { (cell, indexPath, item) in
    // @todo configure the cell's content

    cell.accessories = [
        .disclosureIndicator(),
        .delete()
    ]
}

キーポイント:

  • cell.accessoriesUICellAccessory の配列を受け取ります。
  • システムは disclosure indicator が trailing、delete accessory が leading であることを知っています。
  • delete accessory はデフォルトで editing モード時のみ表示され、編集モード出入りのアニメーションは UIKit が処理します。

(15:51) accessory のデフォルト表示ルールはパラメータで調整できます。下の disclosure indicator は非編集時のみ表示します。

let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { (cell, indexPath, item) in
    // @todo configure the cell's content

    cell.accessories = [
        .disclosureIndicator(displayed: .whenNotEditing),
        .delete()
    ]
}

キーポイント:

  • displayed: .whenNotEditing で accessory の表示状態を変えます。
  • この宣言的設定で cell は通常態と編集態を自動切り替えします。
  • 複雑なリストではナビゲーション、削除、並べ替え、outline 展開を同じ cell accessory システムで扱えます。

重要ポイント

  • 旧 table view リストを UICollectionView に移行する:外観を変えない設定ページやお気に入りページを作る。リスト configuration で plain、grouped、inset-grouped スタイルを再利用し、その後横おすすめ領域やグリッド section を段階的に追加する。
  • iPad sidebar + コンテンツ領域のデータ管理 UI を作る:sidebar または sidebar plain 外観でグループナビを担い、transcript の iPadOS マルチカラム App シーンに適合する。まずナビ列に list configuration を使い、section ごとに横の最近項目や階層グループを追加する。
  • リスト項目に安定した swipe actions を追加する:メール、メモ、お気に入りで「お気に入り」「アーカイブ」を実装する。UICollectionView.CellRegistrationUIContextualAction を設定し、handler は model か安定 identifier を捕まえる。
  • 手書き編集コントロールを accessories に置き換える:フォルダ、タスクリスト、outline に disclosure、delete、reorder などの accessory を追加する。先に cell.accessories を設定し、対応する data source コールバックを接続する。
  • section ごとに情報密度を混在させる:1ページで上部に横の最近項目、中央に outline、下部に通常リストを置く。section provider は section ごとにグリッド、直交スクロールレイアウト、NSCollectionLayoutSection.list(using:layoutEnvironment:) を返せる。

関連セッション

  • Advances in UICollectionView — iOS 14 の collection view 更新を高いレベルで紹介。lists、cell registration、section snapshots を含む。
  • Advances in diffable data sources — section snapshots、outline collection views、sidebar、item-based reordering を深掘りする。
  • Modern cell configuration — list cell のデフォルトコンテンツと状態更新に使う cell content/background configuration を補う。
  • Build for iPad — iPad のマルチカラムレイアウト、sidebar、大画面 UI でのリストの使い方を扱う。

コメント

GitHub Issues · utterances