There are probably a few different ways to do this. Ideally, you won't have to do any type of calculation to simulate the remaining deceleration physics or mess around at all with UIScrollView
's internals. It's error-prone, and it's not likely to perfectly match the UIScrollView
physics everyone's used to.
Instead, for cases where your scroll view is merely a driver for a gesture (i.e., you don't actually need to display anything in it), I think it's best to just have a massively wide scroll view. Set its initial content offset to the center of its content size. In scrollViewDidScroll(_:)
, calculate a percentage of the traversed screen width:
let pageWidth = scrollView.frame.width
let percentage = (scrollView.contentOffset.x % pageWidth) / pageWidth
This will basically loop from 0.0
to 1.0
(moving right) or from 1.0
to 0.0
(moving left) over and over. You can forward those normalized values to some other function that can respond to it, perhaps to drive an animation. Just structure whatever code responds to this such that it appears seamless when jumping from 0.0
to 1.0
or from 1.0
to 0.0
.
Of course, if you need whatever you're looping to occur faster or slower than the normal scroll view speed, just use a smaller or larger fraction of the screen width. I just picked that arbitrarily.
If you're worried about hitting the edges of the scrollable content, then when the scroll view comes to a complete rest1, reset its content offset to the same initial center value, plus whatever remainder of the screen width (or whatever you're using) the scroll view was at when it stopped scrolling.
Given the resetting approach above, even for scroll views where you're hosting visible content, you can effectively achieve an infinitely scrolling view as long as you update whatever view frames/model values to take into account the reset scroll offset.
1 To properly capture this, implement scrollViewDidEndDecelerating(_:)
and scrollViewDidEndDragging(_:willDecelerate:)
, only calling your "complete rest" function in the latter case when willDecelerate
is false
. Always call it in the former case.