Unsolved iOS (while updating) feeds touch events to QUIWindow -> weirdness!
-
environment: Qt/QML 5.15.2, iOS 15.2.1
To solve this bug, I need the help of a developer who really understands iOS event processing.
My app’s QML code responds to a button press by opening a modal message dialog (QtQuick.Dialogs 1.2
MessageDialog
). On iOS Simulator (all iPhone-model runtimes) and on all older-model iPhones, the dialog is painted as expected onceprocessEvents
is called and events are dispatched, but, on newer iPhones (12/13), the event loop never gets those focus and update events and just hangs, waiting for the user to interact with a dialog that wasn’t painted.To debug this, on those three platforms, I followed the traceback down to the point where iOS sends the synthesized touch event to Qt via its Objective-C
QUIWindow
object; there is no Qt code below this point in the stack, so it’s reasonable to think that the call stack from there to the bottom should be the same in all three cases. But it isn’t!In the (failure) case of the newer iPhones only, the call stack looks like this:
-[QUIWindow sendEvent:] -[UIApplication sendEvent:] __dispatchPreprocessedEventFromEventQueue __processEventQueue updateCycleEntry _UIUpdateSequenceRun schedulerStepScheduledMainSection runloopSourceCallback __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ …
And in the other (success) cases, it looks like this:
-[QUIWindow sendEvent:] -[UIApplication sendEvent:] __dispatchPreprocessedEventFromEventQueue __processEventQueue __eventFetcherSourceCallback __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ …
So, it appears, the problem occurs when iOS dispatches the touch event to Qt in the course of its own update cycle rather than the expected way (straight from
CFRunLoop
). My theory, then, is that Qt’sprocessEvents
is blinded to the update events that result from opening its dialog, because iOS thinks its own update cycle isn’t done yet; indeed, if I stop processing Qt events myself and let the stack unwind to let iOS finish its own update cycle, the necessary Qt events are then generated and handled, and the dialog is painted.Caveat: There is C++ code and a nested event loop involved here, which is unavoidable because synchronous C++ code is signaling declarative QML code to run a modal dialog. (The C++ code is a multi-platform procedural library that signals QML in mobile versions, but more C++ code in computer versions, when it needs to interact with the user.) I’m aware that Nested Event Loops Are Bad in QML, but [a] the nested event loop is only created in response to this oddly dispatched touch event, and [b] this code has always worked perfectly on every other device (including Android) and even on iOS 15.2.1 on older devices. Further, it seems that dispatching touch events from inside iOS’s own update cycle (thus forcing any Qt-on-iOS code’s GUI response to eschew
QEventLoop
/processEvents
) is bound to be a problem — the only question there being, did I bring on that questionable iOS habit somehow? -
Hi,
@barbicels said in iOS (while updating) feeds touch events to QUIWindow -> weirdness!:
5.12.2
Not a direct answer sorry but did you try to test your issue with a more recent version of Qt to see if you get into the same kind of issue ? I understand you might be locked to that version of Qt but this would allow to ensure it's something that is affecting other versions of the 5.12 series as well as maybe the 5.15 or even 6.
-
@SGaist
Argh, that was a typo, now edited. It’s 5.15.2 (LTS), open source. -
@barbicels said in iOS (while updating) feeds touch events to QUIWindow -> weirdness!:
@SGaist
Argh, that was a typo, now edited. It’s 5.15.2 (LTS), open source.Damn ! So it's one less hope of it working on a later version. Can you try with 6.2 ?
-
@SGaist
I shall have to try that, but it seems profoundly weird that iOS would dispatch a touch event while spinning its own update cycle, and only on newer hardware (suggesting some sort of race condition). Hard to see how Qt could be responsible for that behaviour, or work around it in any way that doesn’t effectively preclude the use of a Qt event loop as part of the GUI response to the touch event. -
Agreed
By the way, since you can reliably reproduce that behaviour, I think you should consider opening a ticket on the Qt bug report system. Even if not directly Qt's problem, it will at least make it known and possibly pushed upstream as well.
-
-
@SGaist
Using an event filter and category logging rules, I was able to distinguish the success (simulator and older iPhones) and failure (newer iPhones) cases:
The failure case results from the application window receiving anUpdateRequest
event after the user touches the QMLSwitch
item and before the resultingTouchEnd
event causes that item’sonSwitchChanged
handler to execute; in the success case, that update event is processed after the touch event.
That would explain why the iOS run loop is in its update cycle at the time, which in turn explains why the code that runs in response to the touch event doesn’t receive more update events in its nested Qt run loop as would be required to paint the confirmation dialog opened in response to the switch change.
Knowing that Nested Event Loops Are Bad, I have thrown aQt.callLater
into theonSwitchChanged
handler so that the handler returns to the main event loop before JS invokes anyQObject
methods that could create a nested event loop leading to “stacked” QML event handling. This appears to work, so I’ll make the same change in other places where such event-handler “stacking” could happen, and hope for the best.
Thank you for the help and encouragement. -
Thanks for the detailed analysis and workaround !
I still think it's worth opening a ticket to make the situation known.
-
@SGaist
Some later observations:- Porting to Qt 6.2.2 did not fix the problem. A modal dialog (using
QtQuick.Dialogs
1.2MessageDialog
) still does not paint if the C++ method (invoked by theonSwitchChanged
event handler), which signaled QML to post the dialog, then spins its own event loop (to maintain its synchronous semantics, waiting for the user's response to the dialog) rather than returning to the main thread's base event loop. So, I can forget about that as a possible avenue for resolution. - The workaround I described (using
Qt.callLater
so that theonSwitchChanged
event handler can return to the base event loop, deferring the posting of the modal dialog and all logic that depends on the user's response to it) works to a degree, but a run of two or three of those dialogs in sequence causes some further event-handling weirdness that I still have to work out. - Apple's recent macOS 12.2 update did not make any difference. My problem remains, though it's still only happening on newer iPhone models. As I wrote, above, there are other unresolved reports of odd crashes whose traceback shows touch events being dispatched from inside iOS's update cycle, which still strikes me as something that shouldn't happen. Maybe I'll bounce this off my old acquaintance Quinn at Apple Developer Support…
Thanks again for your interest!
- Porting to Qt 6.2.2 did not fix the problem. A modal dialog (using
-
Then I confirm that it is worth a report :-)