UICollectionLayoutListConfigurationのheaderMode=.firstItemInSection観測隊
はじめに
UICollectionView
は UITalbeView
よりも出来ることが多いですよ、ということでAppleが推している UICollectionView
。
まずは次のものを見ていただきたい。あるソースコードとその実行結果(の一部)である。
「甲信越」など、ひとつめのデータがちょっと違う色と大きさで表示されている。これは、1個目のデータをヘッダー扱いにする機能を使っている。これは UICollectionView
に備わっている機能である。今日はこれを観測していきたい。
UICollectionViewのコンフィグレーション
この仕様にするためには UICollectionView
にコンフィグレーションというものを使う。コンフィグレーションとは仕様を細かく設定するものである。コンフィグレーションを使った UICollectionView
のインスタンス生成の様子は次の通り。
var collectionView: UICollectionView = {
var listConfiguration =
UICollectionLayoutListConfiguration(
appearance: .plain)
listConfiguration.headerMode = .firstItemInSection
let simpleLayout =
UICollectionViewCompositionalLayout.list(
using: listConfiguration)
return UICollectionView(
frame: .zero,
collectionViewLayout: simpleLayout)
}()
コンフィグレーションの中でも UICollectionLayoutListConfiguration
を使う。これはiOS14からなのでわりと新しいものである。
ここで listConfiguration.headerMode = .firstItemInSection
というのが、ひとつめのものをヘッダーとして使う設定にしている箇所である。
UICollectionLayoutListConfigurationの設定よって見た目が違う
UICollectionLayoutListConfiguration(appearance: .plain)
の引数に与えるものによってヘッダーのデザインが変わる。
.plain
.grouped
.insetGrouped
.sidebar
.sidebarPlain
sidebarは文字が大きいですな。
新潟を選択状態にした理由は、選択していないと .plain
と .sidebarPlain
はそもそも何が違うのかと思われてしまうから。
タップする
「甲信越」の部分をタップすると通常のセルと同じように collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
が走る。
我々の任務はヘッダーの扱いの調査だったので今日の観測はここまでである。ここからはソースコードの他の部分について書いたもの。
データソース
//リサイクル機能
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> {
(cell, indexPath, identifier) in
var configration = cell.defaultContentConfiguration()
configration.text = identifier
cell.contentConfiguration = configration
}
//データソースの作成
dataSource = UICollectionViewDiffableDataSource<Int, String>
(collectionView: collectionView) {
//セルの内容が決定
(collectionView: UICollectionView, indexPath: IndexPath, identifier: String) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
}
//構成を決める
var snapshot = NSDiffableDataSourceSnapshot<Int, String>()
snapshot.appendSections((0..<prefectures.count).map{ $0 })
for i in 0..<prefectures.count {
snapshot.appendItems(prefectures[i], toSection: i)
}
dataSource.apply(snapshot, animatingDifferences: false)
リサイクル機能では UICollectionViewListCell
で取得できるようにしたので、それが用意されている。その UICollectionViewListCell
インスタンスの defaultContentConfiguration()
は UIListContentConfiguration
を返す。これはセルの中身を設定するもので、文字列や画像などの入れ物があらかじめ用意されている。
ちなみにこの新しいデータソースの書き方の場合 ["北海道", "北海道"]
のように同じデータが2つあるとエラーになる、残念。
Listなんとか、ありすぎじゃね?
-
UICollectionLayoutListConfiguration
UICollectionViewのレイアウトの性質を扱うコンフィグレーション -
UICollectionViewCompositionalLayout.list
レイアウト情報作成メソッド。リスト形式専用のもの。 -
UICollectionViewListCell
セルの型 -
UIListContentConfiguration
セルの性質を扱うコンフィグレーション
この4つの組み合わせ以外を試そうかとも思うがあまり面白い結果にならなさそう。
ViewController全体
import UIKit
class ViewController: UIViewController {
let prefectures = [
["東北", "青森", "岩手", "秋田", "宮城", "山形", "福島"],
["関東", "茨城", "栃木", "群馬", "埼玉", "千葉", "東京", "神奈川"],
["甲信越", "新潟", "長野", "山梨"],
["北陸", "富山", "石川", "福井"],
["東海", "岐阜", "静岡", "愛知", "三重"],
["近畿", "滋賀", "京都", "奈良", "大阪", "和歌山", "兵庫"],
["中国", "鳥取", "島根", "岡山", "広島", "山口"],
["四国", "香川", "徳島", "愛媛", "高知"],
["九州", "福岡", "佐賀", "長崎", "大分", "熊本", "宮崎", "鹿児島"],
]
var collectionView: UICollectionView = {
var listConfiguration = UICollectionLayoutListConfiguration(appearance: .plain)
listConfiguration.headerMode = .firstItemInSection
let simpleLayout = UICollectionViewCompositionalLayout.list(using: listConfiguration)
return UICollectionView(frame: .zero, collectionViewLayout: simpleLayout)
}()
var dataSource: UICollectionViewDiffableDataSource<Int, String>! = nil
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
view.addSubview(collectionView)
configureDataSource()
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
}
}
extension ViewController {
func configureDataSource() {
//リサイクル機能
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { (cell, indexPath, identifier) in
var configration = cell.defaultContentConfiguration()
configration.text = identifier
cell.contentConfiguration = configration
}
//データソースの作成
dataSource = UICollectionViewDiffableDataSource<Int, String> (collectionView: collectionView) {
//セルの内容が決定
(collectionView: UICollectionView, indexPath: IndexPath, identifier: String) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
}
//構成を決める
var snapshot = NSDiffableDataSourceSnapshot<Int, String>()
snapshot.appendSections((0..<prefectures.count).map{ $0 })
for i in 0..<prefectures.count {
snapshot.appendItems(prefectures[i], toSection: i)
}
dataSource.apply(snapshot, animatingDifferences: false)
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("didSelect", prefectures[indexPath.section][indexPath.row])
}
}
Discussion