I would like to be able to predict the final resting offset within a UIScrollView after a flick gesture. It doesn't need to be pixel-accurate, but close enough so that the user does not perceive a difference (i.e. it doesn't move excessively less or more than they are used to).
I know someone will ask, so: Why? I have a table view-like menu control inside a UIScrollView. I would like to make it such that the top-most menu item is fully displayed and flush to the top of the UIScrollView. UIScrollView's paging feature is not quite what I want, because a strong flick doesn't fly past multiples of the view bounds.
Handling normal touch events is easy enough. On touchesEnded:withEvent:
, I can scroll to the nearest full menu item. The hard part is deceleration.
There are two constants for the deceleration rate, UIScrollViewDecelerationRateNormal
and UIScrollViewDecelerationRateFast
. Their values are 0.998 and 0.990 in iPhone OS 3.0. I have tried to figure out the math that Apple uses to slow movement, but I'm coming up empty.
If I can predict with some accuracy the final resting offset, then early during deceleration I can simply use scrollRectToVisible:animated:
to move to an offset with a menu item flush to the top of the view bounds.
Do any math-inclined people know what Apple may be doing during deceleration? Should I collect a bunch of numbers of deceleration events, graph them and come up with something close?
You could watch the speed of scrolling while the UIScrollView is decelerating, and when the speed falls beneath a certain value, snap to the nearest menu item.
You will likely want to implement the
UIScrollViewDelegate
methodsscrollViewDidEndDragging:willDecelerate:
andscrollViewDidScroll:
. When the user lifts their finger to finish a scroll gesture,scrollViewDidEndDragging:willDecelerate:
will be called. Begin monitoring thescrollViewDidScroll:
events, and watch the delta between theUIScrollView
's current position and its previous position, divided by the time delta:When the velocity falls below a minimum value, call
scrollRectToVisible:animated:
on theUIScrollView
.Control UIScrollView's targetContentOffset in iOS 5
iOS5
UIScrollViewDelegate
has a new method:scrollViewWillEndDragging:withVelocity:targetContentOffset:
.This fits perfectly with what you want to do.
Alternative Solution for iOS4 and Lower
Change the
UIScrollView
'sdecelerationRate
toUIScrollViewDecelerationFast
and then inscrollViewDidEndDecelerating
move to the closest "page".The fast deceleration makes the complete stopping / sliding over a little more natural / less obnoxious.
I cannot comment (I guess I need 50 rep), so this is not a full answer, but given the 0.998 and 0.990 values for deceleration, my instincts are that they simply multiply the current velocity by the deceleration rate each frame.