The problem, as Dave pointed out, has to do with run loop modes. Applications will reside mainly in the kCFRunLoopDefaultMode
mode but switch into UITrackingRunLoopMode
when the user starts scrolling and back again when the scroll view finishes decelerating. This appears to be to prevent normal timers from firing during scrolling. (You can see this in Safari where animations stop updating during scroll.)
The mechanics appear to be as follows.
- The
UIApplication
has a stack to keep track of the current run loop mode requested by the application. - When the application starts up, it calls
GSEventRunModal
which in turn callsCFRunLoopRunInMode
with the current run mode for the application,kCFRunLoopDefaultMode
. - When the user starts swiping a scroll view, the scroll view specific pan gesture recognizer enters the Began state and triggers
[[UIApplication sharedApplication] pushRunMode:UITrackingRunLoopMode requester:self]
. - This method pushes the string onto the stack, sees that the run mode is different, and calls
CFRunLoopStop
on the run loop created byGSEventRunModal
. GSEventRunModal
loops and callsCFRunLoopRunInMode
again, this time withUITrackingRunLoopMode
.- When deceleration completes, the gesture recognizer calls
[[UIApplication sharedApplication] popRunMode:UITrackingRunLoopMode requester:self]
and similar steps take place.
The problem with my long running function that calls runUntilDate:
it never terminates so CFRunLoopStop
has no effect. One approach to take would be to swizzle pushRunMode:requester:
and popRunMode:requester:
and do something similar with my own internal logic.
The approach I've actually taken, since I will not be receiving any actual user interaction, is to send the touch move event to the application and check to see what gestureRecognizers are on it. If there is a UIScrollViewPanGestureRecognizer
that entered the Began
phase, the remainder of the run loops for dragging are done with UITrackingRunLoopMode
. Once the touch simulation is complete, I continue to run in that mode until scrollView.dragging == NO
.
You can see it in action at https://github.com/bnickel/KIF/blob/kif-next/Additions/UIView-KIFAdditions.m#L368