The only truly solid solution I have ever found is this:
You actually use TWO collection views ...
Have your "visual" collection view. Which has the actual images in each cell. The visual collection view is the full width of the screen.
On top of that, i.e. directly above it, have you "touch" or "ghost" collection view. The cells are the same size, but the width of the touch collection view is actually only the width of your "real" cells in the "visual" collection view underneath.
The user only ever touches the "ghost" cv on top. And the ghost cv simply moves the "visual" cv.
It's actually remarkably easy to do this once you start setting it up.
All you need is one line of code to drive the underneath "visual" cv from the "ghost" cv, like
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == ghost {
// double trouble magic:
visual.contentOffset.x = ghost.contentOffset.x
}
}
(Note that, more specifically, that line will be something like visual.contentOffset.x = ghost.contentOffset.x - visual.contentInset.left
depending on your own style for setting up the two CVs.)
Once you try this approach, you'll never do it any other way, because it's absolutely rock solid, it literally works exactly as it should - since you're literally using a CV (the top one) that simply is "exactly the mechanism you want", by definition, no ifs ands or buts. It pages to the width of your cells, using obviously exactly the Apple physics/touch and that's that.
Obviously, the top "ghost" one simply has .. blank cells, ie just clear cells.
Incredibly, you just use the same data source for both !! Simply point both CVs at the same view controller for the numberOfItemsInSection
and cellForItemAt
calls!
Give it a try!
Once again, the whole thing is one line of code, given above.
If you think about it, the top "touch" one, the actual CV is as we say only as wide as one of your cells ... however, you want to be able to touch the top "touch" one anywhere on the full width of the screen. Of course, widening the touch area on any view is a very common issue you are familiar with, just use the usual solution, something like ...
class SuperWideCollectionView: UICollectionView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return bounds.insetBy(dx: -5000, dy: 0).contains(point) }
}
It's a great rig, and now the only way we do this.
This is one of the three or four Apple issues where it is "just incredible" they don't have a built in toggle in the relevant views where you can page by "item, not screen width" - but for now they don't!