前置き
特定の実装をする際に、スクロールのハンドリングをしようと考えていました。
Compositional Layouts
では簡単にできると思っていましたが、案外情報がなかったので(英語での情報はちらほら)、自分へのメモ的な部分も含めて記載していきます。
1. Inner Scrollとは?
Inner Scrollという名前があるわけではなく、わかりやすくするために呼んでいます。
このように入れ子の構造になった画面でのスクロールを指します。
2. 現在地を取得する際の問題点
今までは、このような入れ子の構造を実装する場合
-
CollectionView
inTableView
-
ScrollView
+StackView
などで実現できるかと思います。
1 の場合は、中にあるCollectionView
から親にDelegate
を渡してスクロールを検知できます。
2 の場合は、indexPathsForVisibleItems
やvisibleCells
を使って計算することになります。
Compositional Layouts
で入れ子の構造を実装した場合、Delegate
がInnerのスクロールに反応してくれません。
2 と同じようにCollectionView
のプロパティを使って計算することもできますが、とても冗長な実装になります。
3. 解決策
セクションのプロパティである visibleItemsInvalidationHandler
を使うことで解決できます。
この NSCollectionLayoutSectionVisibleItemsInvalidationHandler
の中身ですが(名前がクソ長い)
public typealias NSCollectionLayoutSectionVisibleItemsInvalidationHandler = (
[NSCollectionLayoutVisibleItem],
CGPoint,
NSCollectionLayoutEnvironment
) -> Void
となっています。1つずつ見ていきましょう。
① NSCollectionLayoutVisibleItem
スクロールされた際に表示されている、セルの情報が入っています。
② CGPoint
これはそのままですが、スクロールされた際に表示されている、セルの現在地の座標を取得することができます。
③ NSCollectionLayoutEnvironment
スクロールされた際に表示されている、セルのレイアウト情報が入っています。
現在値を取得
② のCGPointを使って計算することもできます。
または、① の NSCollectionLayoutVisibleItem
にはIndexPath
の情報が含まれているので、こちらを使用する方が内部的に計算がされており、実装は楽になるでしょう。
ただし、使い方に少しだけ注意が必要です。
セルが複数表示されている場合は、余計なIndexも取得されて複数で返ってくるので、その部分の計算は必要になってきます。基本的には、配列の最後に返ってくるIndexが現在地のIndexになります。
終わりに
一見すると便利な API が用意されてそうでしたが、挙動が少し怪しい?感じもあり...
Compositional Layouts
も万能ではないということですね
SwiftUI
もスクロールのハンドリングは自前で実装したりするので、Apple にはこの辺の実装を強化して欲いなぁと思ってしまった感じがあります。
参考文献
- Apple - visibleItemsInvalidationHandler
- UICollectionView CompositionalLayout not calling UIScrollDelegate
- orthogonalScrollingBehavior paging - getting current cell in center
- CompotionalLayouts でセクションごとのcollectionView/cellにアクセスする方法
その他