UIScrollView和 - [NSRunLoop runUntilDate:]

时间:2013-06-28 23:18:56

标签: ios uikit nsrunloop

我正在开发一个在应用程序主线程中运行的UI测试框架(KIF-next)。基本过程是:

  1. 执行一些测试逻辑。
  2. 通过runUntilDate:旋转主循环0.1秒。
  3. 重复。
  4. 这种方法效果非常好。您可以模拟点击事件,可以滑动滑块,观看警报视图的动画效果,scrollToRowAtIndexPath:。除了一个特定的场景,拖动滚动视图,一切正常。

    您可以上下拖动滚动视图,但是当您松开手指时,没有任何反应。此外,触摸事件不再被其他视图识别,您可以做的就是拖动滚动视图。

    如果这个执行模式结束并且代码退出,滚动视图将执行它们的反弹/减速,但这不是一个选项,因为我在执行顺序执行的ocunit中工作。

    我的问题:为什么会发生这种情况以及如何解决?

    我有两个演示:

    演示1:阻止整个应用程序

    使用此代码,您可以测试您的应用程序并确认一切正常,直到滚动。在那之后,没有任何作用。

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        double delayInSeconds = 2.0;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            while (YES) {
                [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
            }
        });
        return YES;
    }
    

    演示2:暂时阻止

    如果您将此代码连接到某个按钮,如果您先点击该按钮然后滚动,您将看到与上述相同的效果。一旦计时器完成,您将看到您的应用程序恢复生机。

    - (IBAction)spinButtonClicked:(id)sender
    {
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];
    }
    

1 个答案:

答案 0 :(得分:8)

正如Dave指出的那样,问题与运行循环模式有关。应用程序将主要驻留在kCFRunLoopDefaultMode模式,但在用户开始滚动时切换到UITrackingRunLoopMode,当滚动视图完成减速时再切换回UIApplication。这似乎是为了防止正常计时器在滚动期间触发。 (您可以在Safari中看到此动画,其中动画在滚动期间停止更新。)

机制似乎如下。

  1. GSEventRunModal有一个堆栈,用于跟踪应用程序请求的当前运行循环模式。
  2. 当应用程序启动时,它会调用CFRunLoopRunInMode,然后使用当前的应用程序运行模式kCFRunLoopDefaultMode调用[[UIApplication sharedApplication] pushRunMode:UITrackingRunLoopMode requester:self]
  3. 当用户开始滑动滚动视图时,滚动视图特定的平移手势识别器进入Began状态并触发CFRunLoopStop
  4. 此方法将字符串压入堆栈,发现运行模式不同,并在GSEventRunModal创建的运行循环上调用GSEventRunModal
  5. CFRunLoopRunInMode循环并再次致电UITrackingRunLoopMode,这次是[[UIApplication sharedApplication] popRunMode:UITrackingRunLoopMode requester:self]
  6. 减速完成后,手势识别器会调用runUntilDate:并执行类似的步骤。
  7. 我的长时间运行函数调用CFRunLoopStop它永远不会终止,因此pushRunMode:requester:无效。一种方法是调动popRunMode:requester:UIScrollViewPanGestureRecognizer并使用我自己的内部逻辑做类似的事情。

    我实际采用的方法,因为我不会接收任何实际的用户交互,就是将触摸移动事件发送到应用程序并检查其上有哪些gestureRecognizer。如果有Began进入UITrackingRunLoopMode阶段,则拖动的剩余运行循环将使用scrollView.dragging == NO完成。触摸模拟完成后,我将继续以该模式运行,直至{{1}}。

    您可以在https://github.com/bnickel/KIF/blob/kif-next/Additions/UIView-KIFAdditions.m#L368

    看到它的实际效果