iOS: Teach event-dispatcher to handle changes to run-loop mode at runtime

UIKit changes the run-loop mode during scrolling to UITrackingMode, which
presumably prioritizes touch events and other sources related to a
smooth scrolling experience. It signals this change by interrupting the
current run-loop pass, and the outer loop is responsible for re-entering
the run-loop in the new mode. Failing to enter the run-loop with this
new mode results in UIScrollViews losing their kinetic feel when
flicking. This can be observed by e.g. bringing up the Emoji keyboard
and scrolling it horizontally.

We keep track of the current run-loop mode by listening for push and pop
notifications on the UIApplication object. The current mode is then used
in our Q_FOREVER-loop when re-entering CFRunLoopRunInMode.

For now we don't add our posted event source or timer source to the
new modes, under the assumption that the system prefers to limit the
number of sources that will fire during scrolling. If this turns out
to give a bad user-experience for Qt applications we should consider
changing it.

Change-Id: I3a612b3cfc77c74b658963057732dc4d61684df8
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@digia.com>
bb10
Tor Arne Vestbø 2013-09-02 19:43:16 +02:00 committed by The Qt Project
parent 880b614c8f
commit 5d926657f4
2 changed files with 77 additions and 2 deletions

View File

@ -55,6 +55,71 @@
#include <UIKit/UIApplication.h>
@interface RunLoopModeTracker : NSObject {
QStack<CFStringRef> m_runLoopModes;
}
@end
@implementation RunLoopModeTracker
- (id) init
{
if (self = [super init]) {
m_runLoopModes.push(kCFRunLoopDefaultMode);
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(receivedNotification:)
name:nil
object:[UIApplication sharedApplication]];
}
return self;
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
static CFStringRef runLoopMode(NSDictionary *dictionary)
{
for (NSString *key in dictionary) {
if (CFStringHasSuffix((CFStringRef)key, CFSTR("RunLoopMode")))
return (CFStringRef)[dictionary objectForKey: key];
}
return nil;
}
- (void) receivedNotification:(NSNotification *) notification
{
if (CFStringHasSuffix((CFStringRef)notification.name, CFSTR("RunLoopModePushNotification"))) {
if (CFStringRef mode = runLoopMode(notification.userInfo))
m_runLoopModes.push(mode);
else
qWarning("Encountered run loop push notification without run loop mode!");
} else if (CFStringHasSuffix((CFStringRef)notification.name, CFSTR("RunLoopModePopNotification"))) {
CFStringRef mode = runLoopMode(notification.userInfo);
if (CFStringCompare(mode, [self currentMode], 0) == kCFCompareEqualTo)
m_runLoopModes.pop();
else
qWarning("Tried to pop run loop mode '%s' that was never pushed!", qPrintable(QCFString::toQString(mode)));
Q_ASSERT(m_runLoopModes.size() >= 1);
}
}
- (CFStringRef) currentMode
{
return m_runLoopModes.top();
}
@end
QT_BEGIN_NAMESPACE
QT_USE_NAMESPACE
@ -120,6 +185,7 @@ QEventDispatcherCoreFoundation::QEventDispatcherCoreFoundation(QObject *parent)
kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting
#endif
)
, m_runLoopModeTracker([[RunLoopModeTracker alloc] init])
, m_runLoopTimer(0)
, m_blockedRunLoopTimer(0)
, m_overdueTimerScheduled(false)
@ -195,11 +261,11 @@ bool QEventDispatcherCoreFoundation::processEvents(QEventLoop::ProcessEventsFlag
ProcessEventsState previousState = m_processEvents;
m_processEvents = ProcessEventsState(flags);
CFStringRef mode = kCFRunLoopDefaultMode;
bool returnAfterSingleSourceHandled = !(m_processEvents.flags & QEventLoop::EventLoopExec);
Q_FOREVER {
CFStringRef mode = [m_runLoopModeTracker currentMode];
CFTimeInterval duration = (m_processEvents.flags & QEventLoop::WaitForMoreEvents) ?
kCFTimeIntervalDistantFuture : kCFTimeIntervalMinimum;
@ -552,5 +618,6 @@ void QEventDispatcherCoreFoundation::invalidateTimer()
}
#include "qeventdispatcher_cf.moc"
#include "moc_qeventdispatcher_cf_p.cpp"
QT_END_NAMESPACE

View File

@ -84,6 +84,12 @@
#include <QtPlatformSupport/private/qcfsocketnotifier_p.h>
#include <CoreFoundation/CoreFoundation.h>
#ifdef __OBJC__
@class RunLoopModeTracker;
#else
typedef struct objc_object RunLoopModeTracker;
#endif
QT_BEGIN_NAMESPACE
class QEventDispatcherCoreFoundation;
@ -206,6 +212,8 @@ private:
RunLoopSource<> m_postedEventsRunLoopSource;
RunLoopObserver<> m_runLoopActivityObserver;
RunLoopModeTracker *m_runLoopModeTracker;
QTimerInfoList m_timerInfoList;
CFRunLoopTimerRef m_runLoopTimer;
CFRunLoopTimerRef m_blockedRunLoopTimer;