Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Swipe gestures with QML Stackview



  • So QML StackView provides a way to navigate back by usin the pop() function. I am looking for a way to go the previous page by simply swiping to the right with UI elements following the fingers of the user (kind of like a SwipeView). How can I achieve this? I have looked around and all I found was this https://github.com/alejoasotelo/SwipePageStackWindow which is an old project that does a similar thing with the old PageStack.
    I think the SwipeView is not really an alternative since my apps would contain at least 20 layers of "pages" so I would manually create and destroy the items to avoid performance issues. I thought about using a MouseArea and implement the gesture myself, but what I want the previous "page" to slide from the left as the user is swiping away the current item to the right. I don't understand how can get the previous "page" to do that. Does StackView provides a way to do that?



  • @raven-worx said in Swipe gestures with QML Stackview:

    Thats the nature of a stack view

    Yeah I think that is the default behaviour, but I think I found a workaround. In the documentation, it is mentioned:

    By default, StackView shows incoming items when the enter transition begins, and hides outgoing items when the exit transition ends. Setting this property explicitly allows the default behavior to be overridden, making it possible to keep items that are below the top-most item visible.

    Then I was able to use that property to make the animation correctly. I believe that Qt Quick should have a default behaviour (it is such a common UI pattern nowadays) to achieve this as it is trivial to do and already built in the native development tools for Android and iOS.
    Anyway here is the working code:

        StackView{
            id: stack
    
            width: window.width
            height: window.height
            initialItem: initialComp
    
            pushEnter: Transition {
                id: pushEnter
                ParallelAnimation {
                    NumberAnimation { property: "x"; from: window.width; to: 0; duration: 400; easing.type: Easing.OutCubic }
                    NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 400; easing.type: Easing.OutCubic }
                }
            }
            pushExit: Transition {
                id: pushExit
                PropertyAction { property: "x"; value: pushExit.ViewTransition.item.pos }
                PropertyAction { property: "y"; value: pushExit.ViewTransition.item.pos }
            }
            popEnter: Transition {
                id: popEnter
                PropertyAction { property: "x"; value: popEnter.ViewTransition.item.pos }
                PropertyAction { property: "y"; value: popEnter.ViewTransition.item.pos }
            }
        }
        Component{
            id: initialComp
            Rectangle{
                color: "purple"
                width: window.width
                height: window.height
                StackView.visible: true
                MouseArea{
                    anchors.fill: parent
                    onClicked: {
                        stack.push(pushComponent)
                    }
                }
            }
        }
    
        Component{
            id: pushComponent
            SwipeView{
                id: swipeView
                currentIndex: 1
                Connections{
                    target: swipeView.contentItem
                    onMovementEnded:{
                        if(swipeView.currentIndex == 0)
                        {
                            stack.pop()
                            console.log("popped")
                        }
                    }
                }
    
                Rectangle{
                    width: window.width
                    height: window.height
                    color: "transparent"
                }
                Rectangle{
                    width: window.width
                    color: "green"
                }
            }
        }
    

    Why do you expect performance issues?

    Well I supposed I could make an implementation that is just as fast as the default StackView, but the documentation mentions:

    It is generally not advisable to add excessive amounts of pages to a SwipeView.

    I would also have to implement quite a lot of the functionality from StackView (like animations), so I am quite happy with this "hack".


  • Moderators

    @daljit97
    you can use a listview. but anyway if you would "slide" to the last page in a stack view you would also have 20 items in the memory.

    Alternatively you could lay a swipe view over the stack view and make sure you only have 2 items in the stack view by doing the push/pop calls yourself appropriately.



  • Alternatively you could lay a swipe view over the stack view and make sure you only have 2 items in the stack view by doing the push/pop calls yourself appropriately.

    Could you explain that a little bit more?



  • So this is what I could come up with. I created a SwipeView which has an empty item and the actual content (left and right respectively), then when the StackView "pushes" to this SwipeView, the user is able to swipe to go back. The only problem is that the content "before" does not become visible until StackView::pop() doesn't get called. Is there anyway around this? Here is my code:

      StackView{
            id: stack
            anchors.fill: parent
            initialItem: initialComp
    
        }
        Component{
            id: initialComp
            Rectangle{
                color: "purple"
                anchors.fill: parent
                MouseArea{
                    anchors.fill: parent
                    onClicked: {
                        stack.push(pushComponent)
                    }
                }
            }
        }
    
        Component{
            id: pushComponent
            SwipeView{
                id: swipeView
                currentIndex: 1
                Connections{
                    target: swipeView.contentItem
                    onContentXChanged: {
                        swipeView.opacity = swipeView.contentItem.contentX/swipeView.contentItem.contentWidth
                    }
                    onMovementEnded:{
                        if(swipeView.currentIndex == 0)
                        {
                            stack.pop()
                        }
                    }
                }
    
                Rectangle{
                    width: window.width
                    height: window.height
                    color: "transparent"
                    onXChanged: console.log("x changed")
                }
                Rectangle{
                    width: window.width
                    color: "green"
                }
    //            onCurrentIndexChanged: if(currentIndex == 0) stack.pop()
            }
        }
    

  • Moderators

    @daljit97

    The only problem is that the content "before" does not become visible until StackView::pop() doesn't get called. Is there anyway around this?

    Thats the nature of a stack view

    @daljit97 said in Swipe gestures with QML Stackview:

    I think the SwipeView is not really an alternative since my apps would contain at least 20 layers of "pages" so I would manually create and destroy the items to avoid performance issues

    Why do you expect performance issues?



  • @raven-worx said in Swipe gestures with QML Stackview:

    Thats the nature of a stack view

    Yeah I think that is the default behaviour, but I think I found a workaround. In the documentation, it is mentioned:

    By default, StackView shows incoming items when the enter transition begins, and hides outgoing items when the exit transition ends. Setting this property explicitly allows the default behavior to be overridden, making it possible to keep items that are below the top-most item visible.

    Then I was able to use that property to make the animation correctly. I believe that Qt Quick should have a default behaviour (it is such a common UI pattern nowadays) to achieve this as it is trivial to do and already built in the native development tools for Android and iOS.
    Anyway here is the working code:

        StackView{
            id: stack
    
            width: window.width
            height: window.height
            initialItem: initialComp
    
            pushEnter: Transition {
                id: pushEnter
                ParallelAnimation {
                    NumberAnimation { property: "x"; from: window.width; to: 0; duration: 400; easing.type: Easing.OutCubic }
                    NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 400; easing.type: Easing.OutCubic }
                }
            }
            pushExit: Transition {
                id: pushExit
                PropertyAction { property: "x"; value: pushExit.ViewTransition.item.pos }
                PropertyAction { property: "y"; value: pushExit.ViewTransition.item.pos }
            }
            popEnter: Transition {
                id: popEnter
                PropertyAction { property: "x"; value: popEnter.ViewTransition.item.pos }
                PropertyAction { property: "y"; value: popEnter.ViewTransition.item.pos }
            }
        }
        Component{
            id: initialComp
            Rectangle{
                color: "purple"
                width: window.width
                height: window.height
                StackView.visible: true
                MouseArea{
                    anchors.fill: parent
                    onClicked: {
                        stack.push(pushComponent)
                    }
                }
            }
        }
    
        Component{
            id: pushComponent
            SwipeView{
                id: swipeView
                currentIndex: 1
                Connections{
                    target: swipeView.contentItem
                    onMovementEnded:{
                        if(swipeView.currentIndex == 0)
                        {
                            stack.pop()
                            console.log("popped")
                        }
                    }
                }
    
                Rectangle{
                    width: window.width
                    height: window.height
                    color: "transparent"
                }
                Rectangle{
                    width: window.width
                    color: "green"
                }
            }
        }
    

    Why do you expect performance issues?

    Well I supposed I could make an implementation that is just as fast as the default StackView, but the documentation mentions:

    It is generally not advisable to add excessive amounts of pages to a SwipeView.

    I would also have to implement quite a lot of the functionality from StackView (like animations), so I am quite happy with this "hack".


  • Moderators

    @daljit97 said in Swipe gestures with QML Stackview:

    I believe that Qt Quick should have a default behaviour (and such a common UI pattern nowadays)

    it has... but you just dont like it... like you already mentioned in your first post

    @daljit97 said in Swipe gestures with QML Stackview:

    Well I supposed I could make an implementation that is just as fast as the default StackView, but the documentation mentions:

    It is generally not advisable to add excessive amounts of pages to a SwipeView.

    20 pages are not "excessive", unless they do not show some overloaded pages with tens to hundreds(?) items each.

    But you could have simply tried it and check if there are any performance considerations at all



  • @raven-worx said in Swipe gestures with QML Stackview:

    @daljit97 said in Swipe gestures with QML Stackview:

    I believe that Qt Quick should have a default behaviour (and such a common UI pattern nowadays)

    it has... but you just dont like it... like you already mentioned in your first post

    @daljit97 said in Swipe gestures with QML Stackview:

    Well I supposed I could make an implementation that is just as fast as the default StackView, but the documentation mentions:

    It is generally not advisable to add excessive amounts of pages to a SwipeView.

    20 pages are not "excessive", unless they do not show some overloaded pages with tens to hundreds(?) items each.

    But you could have simply tried it and check if there are any performance considerations at all

    No, the default behaviour needs to be changed or an option needs to be added because on mobile platforms the use of buttons to go back is cumbersome (it is acceptable for Android, but for iOS a gesture based implementation is definitely needed). There is a big need for Qt developers to implement this and I am sure that many people find it odd that Qt hasn't got this. Few people have already asked for something similar to what I asked, for example look here:
    https://forum.qt.io/topic/72499/ios-app-in-qml-how-to-navigate-backward-through-stackview-via-swipe-gestures
    https://forum.qt.io/topic/56296/stackview-pop-a-page-by-swiping

    As for SwipeView , I haven't gone through that route because I have some WebView in some of the pages and thought that could affect performance. I suppose you are right that I could have tested that, but I also needed some functionalities provided by StackView which I would have had to reimplement.


Log in to reply