Reputation: 2246
I currently have a UICollectionView
using UICollectionViewCompositionalLayout
. I would like to animate some views within the current visible cells while scrolling / scrolling stops.
Unfortunately it seems setting orthogonalScrollingBehavior
on a section to anything but .none
hijacks the UICollectionView
accompanying UIScrollView
delegate methods.
Was wondering if there're any current workaround for this? To get the paging behaviour and UIScrollView
delegate?
enum Section {
case main
}
override func awakeFromNib() {
super.awakeFromNib()
collectionView.collectionViewLayout = createLayout()
collectionView.delegate = self
}
func configure() {
snapshot.appendSections([.main])
snapshot.appendItems(Array(0..<10))
dataSource.apply(snapshot, animatingDifferences: false)
}
private func createLayout() -> UICollectionViewLayout {
let leadingItem = NSCollectionLayoutItem(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
)
leadingItem.contentInsets = .zero
let containerGroup = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)
),
subitems: [leadingItem])
let section = NSCollectionLayoutSection(group: containerGroup)
section.orthogonalScrollingBehavior = .groupPaging // WOULD LIKE PAGING & UISCROLLVIEW TO ALSO BE FIRED
let config = UICollectionViewCompositionalLayoutConfiguration()
config.scrollDirection = .horizontal
let layout = UICollectionViewCompositionalLayout(section: section, configuration: config)
return layout
}
extension SlidingCardView: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// THIS IS FIRED BUT UISCROLLVIEW METHODS NOT
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
print(111)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
print("1111111")
}
}
Upvotes: 14
Views: 5104
Reputation: 492
Here is a solution for determining which cell is in the center of the screen:
section.visibleItemsInvalidationHandler = { [weak self] visibleItems, point, environment in
guard let self = self else { return }
for visibleCell in self.collectionView.visibleCells {
let collectionViewCenterPoint = self.collectionView.center
if let relativePoint = visibleCell.superview?.convert(collectionViewCenterPoint, from: nil),
visibleCell.frame.contains(relativePoint)
{
// visibleCell is in the center of the view.
} else {
// visibleCell is outside the center of the view.
}
}
}
Upvotes: 3
Reputation: 9973
Following @Stoyan answer, I fine tuned the class to be compatible with producition code by not looking for private APIs. Simply looking at all UIScrollView
subclasses.
Also I think it's better to update the delegates during collection reload as you might not have the full view hierarchy yet when setting the delegate.
Finally, the class now recursively looks for UIScrollView
so nothing is ever missed.
final class OrthogonalScrollingCollectionView: UICollectionView {
override func reloadData() {
super.reloadData()
scrollViews(in: self).forEach { scrollView in
scrollView.delegate = delegate
}
}
override func reloadSections(_ sections: IndexSet) {
super.reloadSections(sections)
scrollViews(in: self).forEach { scrollView in
scrollView.delegate = delegate
}
}
fileprivate func scrollViews(in subview: UIView) -> [UIScrollView] {
var scrollViews: [UIScrollView] = []
subview.subviews.forEach { view in
if let scrollView = view as? UIScrollView {
scrollViews.append(scrollView)
} else {
scrollViews.append(contentsOf: self.scrollViews(in: view))
}
}
return scrollViews
}
}
Upvotes: 0
Reputation: 6212
You may just want to use visibleItemsInvalidationHandler
callback of your NSCollectionLayoutSection
it acts like the UIScrollViewDelegate
it will be invoked each time the section scrolls
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .groupPagingCentered
section.visibleItemsInvalidationHandler = { (visibleItems, point, env) -> Void in
print(point)
}
Upvotes: 13
Reputation: 1305
Setting orthogonalScrollingBehavior
to a section, embeds an internal _UICollectionViewOrthogonalScrollerEmbeddedScrollView
which handles the scrolling in a section. This internal scrollview is added as a subview to your collection view.
When you set yourself as a delegate
to your collection view you should receive the scroll view delegate callbacks BUT ONLY for the main collection view, that scrolls between the sections and not the items in a section. Since the internal scrollviews (which may also be collectionViews, not sure) are completely different instances and you are not setting yourself as a delegate to them, you are not receiving their callbacks.
but if you are curious and you want to experiment with that you could use this 'hacked' collectionView class:
import UIKit
final class OrtogonalScrollingCollectionView: UICollectionView {
override var delegate: UICollectionViewDelegate? {
get { super.delegate }
set {
super.delegate = newValue
subviews.forEach { (view) in
guard String(describing: type(of: view)) == "_UICollectionViewOrthogonalScrollerEmbeddedScrollView" else { return }
guard let scrollView = view as? UIScrollView else { return }
scrollView.delegate = newValue
}
}
}
}
that would set your delegate to all internal scrollview that come with the orthogonal sections. You should not be using this in production environment, because there is no guarantee that Apple will keep the inner workings of the collection views the same way so this hack may not work in the future, plus you might get rejected for using private APIs in UIKit when you submit a build for release.
Upvotes: 14