iOS App in QML how to navigate backward through StackView via swipe gestures
-
Hi together,
I am developing an crossplatform compatible (iOS, Android; maybe later also windows phone) app prototype for our company. I am completely new to QML programming.
In my main.qml file I use a StackView which is used for the basic navigation through views in the app. For the android like backward navigation I can simply listen to the "Keys.onReleased" event to pop the current view.
For the iOS implementation of the app I want to use the swipe gesture for navigating to the previous view. What way would you suggest to achieve such functionality?
I currently have an (not completely working approach) where I am using an "MouseArea" as a child of my StackView (with z: 100) to detect the swipe gesture. The navigation works, but I am in trouble to use the controls (like buttons, list, ...) underneath the MouseArea.
My current StackView looks like that:
StackView { id: mainStackLayout anchors.fill: parent focus: true //handle the android back button Keys.onReleased: { if (event.key === Qt.Key_Back && mainStackLayout.depth > 1) { mainStackLayout.pop() event.accepted = true } } //and the ios back swipe MouseArea { id: globalMouseArea anchors.fill: parent hoverEnabled: true z: 100 //propagateComposedEvents: true preventStealing: true property real velocity: 0.0 property int xStart: 0 property int xPrev: 0 property bool tracing: false onPressed: { if(mainStackLayout.depth > 1) { xStart = mouse.x xPrev = mouse.x velocity = 0 tracing = true mouse.accepted = true } else { mouse.accepted = false } } onPositionChanged: { print('position changed') //this routine sets the velocity which is checked in the onReleased handler if (!tracing) { mouse.accepted = false return } var currVel = (mouse.x-xPrev) velocity = (velocity + currVel)/2.0 xPrev = mouse.x mouse.accepted = true } onReleased: { print('released') if(!tracing) { mouse.accepted = false return } print('velocity: ' + velocity) tracing = false if (velocity > 15 && mouse.x > parent.width * 0.2 ) { print('Detect swipe') mouse.accepted = true mainStackLayout.pop() } } } }
But as I said it steals the input events from the controls underneath. I tried to get rid of this problem due simulating mouse clicks in case of the user releases the mouse without making an valid gesture, but I failed to implement such a mouse simulating. I tried to send mouseEvents from c++ to several QML objects, but they never received an event.
For simulating the MouseEvents I tried things like
QCoreApplication::instance()->sendEvent(findApplicationWindow(), &mouseEvent);
where the mouseEvent is an left click and findApplicationWindow() returns the instance of the "QQuickApplicationWindow". In the net I found something like
QMouseEvent mouseEvent(QEvent::MouseButtonPress, QPointF(105, 25), Qt::LeftButton, 0, Qt::NoModifier); QGraphicsSceneMouseEvent pressEvent(QEvent::GraphicsSceneMousePress); pressEvent.setScenePos(QPointF(105, 25)); pressEvent.setButton(Qt::LeftButton); pressEvent.setButtons(Qt::LeftButton); QApplication::sendEvent(object, &pressEvent); QGraphicsSceneMouseEvent releaseEvent(QEvent::GraphicsSceneMouseRelease); releaseEvent.setScenePos(QPointF(105, 25)); releaseEvent.setButton(Qt::LeftButton); releaseEvent.setButtons(Qt::LeftButton); QApplication::sendEvent(object, &releaseEvent);
Which is although not working.
But, all in all, I think it is a bad solution to simulate mouse clicks in my case.
Maybe someone out here has already an working solution for this case or an advise for me. That would really be helpful :-)
Thank you in advance!
Greetings
eumel1990 -
Hi @eumel1990
I currently have an (not completely working approach) where I am using an "MouseArea" as a child of my StackView (with z: 100) to detect the swipe gesture.
Instead of maintaining the
z
order you can makeMouseArea
as the root element and makeStackView
child of it so that you propagate only those events beneath which you require.MouseArea { ... StackView { ... } }
Can SwipeView be an alternative to
StackView
in your case ?Also Since
Qt 5
there is no relation betweenQGraphics*
classes and QtQuick. The backend implemenation is now scenegraph based. So sending events in that way wont work. -
Hi p3c0
that's brilliant. I just did a short test until now, but changing the parent-child relation like you suggested worked for me.
Now my StackView looks like that:
MouseArea { id: globalMouseArea anchors.fill: parent hoverEnabled: true //z: 100 //propagateComposedEvents: true preventStealing: true property real velocity: 0.0 property int xStart: 0 property int xPrev: 0 property bool tracing: false onPressed: { // mouse.accepted = false //mouseEventSender.sendMouseEvent(mouse.x, mouse.y, globalMouseArea) if(mainStackLayout.depth > 1) { xStart = mouse.x xPrev = mouse.x velocity = 0 tracing = true mouse.accepted = true } else { mouse.accepted = false } } onPositionChanged: { print('position changed') //this routine sets the velocity which is checked in the onReleased handler if (!tracing) { mouse.accepted = false return } var currVel = (mouse.x-xPrev) velocity = (velocity + currVel)/2.0 xPrev = mouse.x mouse.accepted = true } onReleased: { print('released') if(!tracing) { mouse.accepted = false return } print('velocity: ' + velocity) tracing = false if (velocity > 15 && mouse.x > parent.width * 0.2 ) { print('Detect swipe') mouse.accepted = true mainStackLayout.pop() } /*else { mouse.accepted = false }*/ } StackView { id: mainStackLayout anchors.fill: parent focus: true //handle the android back button Keys.onReleased: { if (event.key === Qt.Key_Back && mainStackLayout.depth > 1) { mainStackLayout.pop() event.accepted = true } } } }
I know the SwipeView, but as far as I understood, it's navigating in a line (like if you swiping through pictures). I am using the StackView for the complete App and, for later implementation steps, the backward swipe should only be enabled for the iOS implementation.
p3c0 you really helped me a lot, I am really grateful, Thank you!!
Greetings
eumel1990 -
@eumel1990 You're Welcome :)
-
Sry, but I have to ask again. What is the reason for this behaviour? Why does the change of the parent/child relation of MouseArea/StackView cause that change of the input event mechanics?
Can you give an advise for an modern book about QML and/or App development with Qt? I already have "Getting started with Qt Quick" from Paolo Sereno (it's about Qt 5.6) which is like a short tutorial.
Thanks in advise
greetings
eumel1990 -
Sry, but I have to ask again. What is the reason for this behaviour? Why does the change of the parent/child relation of MouseArea/StackView cause that change of the input event mechanics?
It is better to have a the main Item to listen to mouse events and then propagate those to individual items rather than each item grab the events and propagate them to other child or siblings.
Can you give an advise for an modern book about QML and/or App development with Qt? I already have "Getting started with Qt Quick" from Paolo Sereno (it's about Qt 5.6) which is like a short tutorial.
Well I guess most of us here have learned Qt/QML through the excellent official documentation so I would first suggest the docs itself. Another one is
http://qmlbook.github.io/ -
Sorry for replying to a topic so old, however, I needed to implement this behavior and made some modifications to the code in this forum. To help someone avoid loosing time, I'll post my modifications.
Summary of modifications:
- Created separate QML item to implement swipe gesture (only enabled on iOS)
- Played with the "mouse.accepted" assignments to get same behavior without drastically affecting full-page Flickable-based items
- Instead of filling the whole stack view, we create the object after the rest of the UI items, and anchor it to the left part of the screen.
- We set a relatively small width to avoid issues with Flickables, ListViews, GridViews, etc.
First, I created a new QML file named BackGestureDetector.qml, with the following code:
import QtQuick 2.12 MouseArea { width: 32 hoverEnabled: true scrollGestureEnabled: false propagateComposedEvents: true enabled: Qt.platform.os === "ios" signal backGestureDetected() property int xPrev: 0 property int xStart: 0 property real velocity: 0.0 property bool tracing: false property bool allowedToWork: false onPressed: { if (allowedToWork) { xStart = mouse.x xPrev = mouse.x velocity = 0 tracing = true mouse.accepted = true } else mouse.accepted = false } onPositionChanged: { if (!tracing) { mouse.accepted = false return } var currVel = (mouse.x - xPrev) velocity = (velocity + currVel)/2.0 xPrev = mouse.x mouse.accepted = false } onReleased: { if (!tracing) { mouse.accepted = false return } tracing = false if (velocity > 15 && mouse.x > parent.width * 0.2) backGestureDetected() mouse.accepted = false } }
Finally, I integrate it to the UI as follows:
import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 ApplicationWindow { id: app // // Set window geometry // width: 800 height: 600 visible: true title: qsTr("Swipe gestures example") // // Page display // StackView { id: stackView focus: true initialItem: page1 anchors.fill: parent Page { id: page1 visible: false width: parent.width height: parent.height Button { text: qsTr("Go to page 2") anchors.centerIn: parent onClicked: stackView.push(page2) } } Page { id: page2 visible: false width: parent.width height: parent.height Label { text: qsTr("Page 2") anchors.centerIn: parent } } } // // Swipe back to navigate // BackGestureDetector { allowedToWork: stackView.depth > 1 onBackGestureDetected: stackView.pop() anchors { top: parent.top left: parent.left bottom: parent.bottom } } }
Any comments are welcome, hope everyone is safe regarding the pandemic.