【Swift】CompotionalLayouts でセクションごとのcollectionView/cellにアクセスする方法
暑いですね、しょーです。
前々回と前回に引き続きCollectionViewのCompositionalLayoutsの理解を深めるマンになってます。
【Swift】iOS13のCollectionView、CompositionalLayoutsでモダンにレイアウトする
【Swift】Compositinal Layouts を複数の型に適応させる方法
今回もCompositionalLayoutsを使用する上でのTipsを紹介しようと思います。
sectionで作成した埋め込みCollectionViewにdelegateメソッドが使えない件
前々回の基本的な使い方で作成したCollectionViewはこのようなレイアウトになっています。
このセクション1に値するCollectionViewに対して、scrollViewDidEndDragging(_:)
などのデリゲートメソッドを使用したい時がありました。
しかしCompositionalLayoutsを実装する場合、一つのCollectioinViewインスタンスを使用してセクションごとにレイアウトを組んでいます。
上記の実装の場合セクションが2つありますが、厳密にはCollectionViewを内部に埋め込んでいるわけではありません。
なのでセクションごとのCollectionViewにdelegateを行う、という概念では解決できません。(delagateメソッドは使用できます)
ヒエラルキーを見てみましょう。
内部はCollectionViewではなくprivateなscrollView
階層を確認してみると、横スクロール部分はこのようになっています。
_UICollectionViewOrthogonalScrollerEmbeddedScrollView
というアクセスができないプライベートなScrollViewとしてレイアウトされているのがわかります。
この時点でcollectionView.delegateの付与対象でないことがわかりますね。
visibleItemsInvalidationHandler を利用する
解決策の一つとして、NSCollectionLayoutSection
でsectionを作成する際、section.visibleItemsInvalidationHandler
を利用することで解決できます。
やりたいことによって解決できることが限られるかもしれませんが、いくつか例で検証してみよう!
特定のセクションの特定のセルにアクセスしたい場合
例えば、現在表示されているセルにアクセスしてアニメーション等を設定したい場合。
private func generateHorizontalLayout() -> NSCollectionLayoutSection {
// item
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(470))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// group
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(470))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
// secction
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .paging
// sectionに対してvisibleItemsInvalidationHandlerを使用することでvisibleItemsにアクセスできる
section.visibleItemsInvalidationHandler = { [weak self] visibleItems, point, environment in
visibleItems.forEach({ item in
guard let cell = collectionView.cellForItem(at: item.indexPath) else { return }
UIView.animate(withDuration: 0.3) {
cell.transform = ...
}
})
}
return section
}
上記のようにすると表示されているセルに対してアニメーションを設定できたりします。
スクロールを検知してpageControlの表示と表示セルのインデックスを同期したい場合
自分がハマっていたのはこちらなのですが、私は冒頭でも書いたscrollViewのデリゲートメソッドを利用してpageControl.currentPage
に現在表示されているセルのインデックスを当てはめたかったのです。
その場合もvisibleItemsInvalidationHandler
を使うことで解決できました。
private func generateHorizontalLayout() -> NSCollectionLayoutSection {
// item
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(470))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// group
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(470))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
// secction
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .paging
// sectionに対してvisibleItemsInvalidationHandlerを使用することでvisibleItemsにアクセスできる
section.visibleItemsInvalidationHandler = { [weak self] visibleItems, point, environment in
self?.pageControl.currentPage = visibleItems.last!.indexPath.row
}
return section
}
単純にvisibleItemsを利用することで、表示セルのインデックスが取得できます。
こちらをpageControl.currentPage
に当てはめることで反映されます。
横スクロールセルのタップ検知
この場合は普通のcollectionView.delegate
で解決できます。
単純にdidSelectItemAt
を利用し、中でindexPatth.section
を使用することで各セクションのセルにアクセスできます。
enum Section: CaseIterable {
case firstSection
case secondSection
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let sectionType = Section.allCases[indexPath.section]
switch sectionType {
case .firstSection:
let cell = self.collectionView.dequeueReusableCell(for: indexPath, as: ApplianceHorizontalCell.self)
cell.delegate = self
case .secondSection:
let cell = self.collectionView.dequeueReusableCell(for: indexPath, as: ArticleCell.self)
cell.delegate = self
}
}
CompositionalLayoutsを実装するときはenum
でセクションごとに分けて実装すると思うので、そのenum
ごとに分岐をすれば横スクロールの内蔵されたセルにも問題なくアクセスが可能です。
まとめ
頭に入れておきたいのはまとめると
- CompositionalLayoutsを実装する際、横スクロール&縦スクロールなど複数セクションを持っている場合はその分のCollectionViewが作成されているわけでは無い
- didSelectItemAtなどのCollectionViewベースのdelagateメソッドはenum + indexPath.sectionの分岐で解決できる
- scrollviewベースのdelegateメソッド(endDraggingなど)はセクションへのアクセスが難しいのでvisibleItemsInvalidationHandlerを利用する
の3つですかね。
この辺を抑えていれば、ある程度特定セルの対応は行えそうです!
いやいや、だいぶCollectionViewに詳しくなってきましたね。
この調子で使い倒していきたいと思います。
参考になれば幸いだぜ。
今日は以上だ、3話連続でCollectionViewの話題だったな!
お疲れっす!
じゃーな!
ディスカッション
コメント一覧
まだ、コメントがありません