Swiped Pages App

June 1, 2016 — 4 Comments

Overview

This is part 3 of a series of demo apps helping you to understand Qt 5.7 for x-platform development on Android and iOS.

You should have read the blogs about my first app using Qt 5.7 Material Style and my second app (StackView Example) before reading on.

This time I’ll explain HowTo use SwipeView – one of the new Qt Quick Controls 2.

Coming from BlackBerry10 Cascades ? SwipeView is one of the less things I ever missed at Cascades. In Apps where I had to implement a Wizard going through some Pages back and force I used Container visibility to make this work.

You should have installed Qt 5.7 RC.

New to Qt 5 development ? Please read my blog series from the beginning to learn some basic stuff and to understand this app.

android_wizard_1

From screenshot above you know about the Business Rules for this SwipeView Example App:

  • SwipeView is used to implement a Wizard
  • There are 5 Pages the User must go through from 1 –> 2 –> 3 –> 4 –> 5
  • A Page must be valid before going to the next Page
  • Validation can be simulated for each Page using the Floating Action Button (FAB)

Also – same as for StackView Example App –

  • When a Page is loaded: call init()
  • Before destroyed: call cleanup()

The Source

This app can be downloaded as Open Source from  github: https://github.com/ekke/swiped_pages_x

Project structure and .pro

The project structure is similar to my StackView Example App – we’re using also 5 Pages here for SwipeView to make it easy for you to learn about the different ways to navigate through a bunch of Pages. Pages are named same as for the StackView app, but sourcecode is slightly different.

main.cpp and ApplicationUI (C++) are also similar to the other apps (One Page Example App, StackView Example App)

ApplicationWindow (main.qml)

main.qml imports, ApplicationWindow and header are similar to stacked-pages-x app – but header is using another ToolBar from Sources/common/SwipeTextTitle:

header: SwipeTextTitle {
    id: titleBar
    text: navPane.currentItem? navPane.currentItem.title : qsTr("A simple Swiped - Pages APP")
}

SwipeTextTitle’s text is bound to currentItem from SwipeView and text changes if another Page becomes the current page. The ‘BACK’ ToolButton is slightly different to StackTextTitle:

ToolButton {
    enabled: navPane.currentIndex > 0
    focusPolicy: Qt.NoFocus
    Image {
        id: backImageImage
        visible: navPane.currentIndex > 0
        anchors.centerIn: parent
        source: "qrc:/images/"+iconOnPrimaryFolder+"/arrow_back.png"
    }
    onClicked: {
        navPane.onePageBack()
    }
}

BACK Button will only work if currentIndex > 0 – so on the very first Page the Button disappears.

The options menu (three dots at right side from ToolBar) looks same as for StackView Example:

  • switch between dark and light theme
  • select primary color
  • select accent color

android_options_menu

main.qml also contains the Popup to select the colors – already described in one-page-x app.

Four other controls we’ll find in main.qml:

  • SwipeView
  • PageIndicator
  • Floating Action Button (FAB)
  • PopupInfo

ApplicationWindow –> FAB

The FAB always stays on top on a fixed position and is defining in main.qml besides SwipeView.

FloatingActionButton {
    id: fab
    visible: !navPane.pageValidation[navPane.currentIndex]
    property string imageName: "/done.png"
    z: 1
    anchors.margins: 16
    anchors.right: parent.right
    anchors.bottom: parent.bottom
    imageSource: "qrc:/images/"+iconOnPrimaryDarkFolder+imageName
    backgroundColor: primaryDarkColor
    onClicked: {
        // simulating check isValid()
        // only if valid next page can be swiped to
        navPane.pageValidation[navPane.currentIndex] = 1
        navPane.validationChanged()
        visible = false
        // Attention: since 5.7 RC you MUST RESET FOCUS
        // otherwise Shortcuts not working well
        resetFocus()
    }
} // FAB
SwipeView {
    id: navPane
    signal validationChanged()
	// ...
    currentIndex: 0
    // currentIndex is the NEXT index swiped to
    onCurrentIndexChanged: {
        fab.visible = !pageValidation[currentIndex]
	    // ...
    }
}
// we can loose the focus if Menu or Popup is opened
function resetFocus() {
    navPane.focus = true
}

Please notice: if clicked on FAB you must reset the Focus to use Shortcuts properly. This is new with 5.7 RC.

Tapping on the FAB does some tasks:

  • Sets current Page as valid: navPane.pageValidation[navPane.currentIndex] = 1
  • Emits a SIGNAL: validationChanged()
  • Makes FAB invisible for current Page

Please take a look at FAB visibility: Tapping on FAB makes the Button invisible, swiping to another Page changes currentIndex and Visibility will be checked again for current Page.

Explore the sourcecode for the Pages: they’re connected to the SIGNAL to enable/disable Buttons. Here’s from PageTwo.qml:

Connections {
    target: navPane
    onValidationChanged:  {
        button3.enabled = navPane.pageValidation[1]
        button5.enabled = navPane.pageValidation[2] && navPane.pageValidation[3]
    }
}

ApplicationWindow –> PageIndicator

It’s a good idea to show the User the current Page and how many Pages exist.

Simply bind a PageIndicator to your SwipeView:

PageIndicator {
    id: pageIndicator
    count: navPane.count
    currentIndex: navPane.currentIndex
    anchors.bottom: navPane.bottom
    anchors.horizontalCenter: parent.horizontalCenter
}

See PageIndicator and FAB / enabled-disabled Buttons in action:

button_enabled_disabled

 

ApplicationWindow –> SwipeView

Our Example uses 5 Pages:

swiping

In most cases your Pages will be defined as static Pages as described in the SwipeView docs.

I have developed some Business Apps for Enterprise Customers where Wizards are really complex with more or less business logic and many controls inside. My goal is always to develop performant apps, so I’m always looking to do it a dynamic way:

Only instantiate Controls / Components you really need or have to show in UI.

In StackView Example App we have seen that StackView manages this dynamic behavior for you and loads / unloads Pages as they’re pushed / popped.

A SwipeView works different: it’s like placing some sheets of paper side by side and you move the content right/left.

SwipeView Example App uses Loader to be dynamic.

Dynamic SwipeView -> Loader

Here’s a short overview of Dynamic SwipeView using Loader:

dynamic_loading

While swiping through the Pages only the currently visible Page plus one Page back and one Page ahead are loaded. The image above shows a snapshot while Page 3 (index 2) is currentItem: Page 2 and Page 4 are loaded and Page 1 and Page 5 are unloaded.

Swiping in-action:

swiped_back_forward_from_3

Here’s one of the Loader:

loader

Notice: the Page inside a Loader must be accessed via item property if loaded.

Hint: All Properties stored inside a Page will be destroyed if unloaded. That’s the reason why the validation property is stored outside – in this case at StackView:

// 0: validation failed, 1: validation done
property var pageValidation: [0,0,0,0,0]

The Loader for Page 3 (Index 2):

Loader {
    // index 2
    id: pageThreeLoader
    property string title: active? item.title:"..."
    active: navPane.currentIndex == 1 || navPane.currentIndex == 2 || navPane.currentIndex == 3
    source: "pages/PageThree.qml"
    onLoaded: item.init()
}

active‘ is bound to currentIndex and – in this case – is active when index 1, 2 or 3 (Page 2, 3 or 4)

As soon as a Page gets loaded, the init() function was called.

Unfortunately there’s no ‘aboutToUnload’ or so to call the cleanup() function. Instead I’m using Component.onDestruction inside the Page:

Component.onDestruction: {
    cleanup()
}

SwipeView – Navigation Back and Forward

Similar to StackView Example you can go different ways to navigate through the Pages:

  • click on a (Raised or Flat) Button to go to next or previous Page
  • click on ‘Back’ from ToolBar to go one Page back
  • click on Android System ‘Back’ key to go one Page back
  • swipe using your fingers to go back or forward
  • use Shortcuts (see StackView example about Shortcuts)

Unfortunately there’s no SIGNAL or so to know that User reached first or last Page. (Will do Bugreport)

Using Android BACK, ToolBar BACK or Shortcuts I implemented a Popup to let the user know that first or last püage reached:

android_first_page_reached

ApplicationWindow –> PopupInfo

PopupInfo is defined in main.qml:

PopupInfo {
    id: popupInfo
    onAboutToHide: {
        popupInfo.stopTimer()
        resetFocus()
    }
} // popupInfo

You’ll find PopupInfo under /common/PopupInfo.qml:

Popup {
    id: popup
    property alias text: popupLabel.text
    property alias buttonText: okButton.text
	// ... the Controls
    onAboutToHide: {
        stopTimer()
    }
    onAboutToShow: {
        closeTimer.start()
    }
    Timer {
        id: closeTimer
        interval: 3000
        repeat: false
        onTriggered: {
            popup.close()
        }
    }
    function stopTimer() {
        closeTimer.stop()
    }
} // popup

PopupInfo uses a Timer to auto-hide if User doesn’t tap on the Popup or outside.

Coming from BlackBerry10 Cascades ? Timer (QTimer) is available as a Type without any extra declaration. Also there’s no attachedObjects[].

PopupInfo also is used while swiping if next Page isn’t valid:

onCurrentIndexChanged: {
    fab.visible = !pageValidation[currentIndex]
    if (lastIndex == currentIndex) {
        return
    }
    if (lastIndex > currentIndex) {
        // swiping back
        lastIndex = currentIndex
        return
    }
    // swiping forward
    for (var i = lastIndex; i <= currentIndex; i++) {
        if(!pageValidation[i]) {
            if(i < currentIndex) {
                pageNotValid(i+1)
                lastIndex = i
                currentIndex = i
                return
            }
        }
    } // for
    lastIndex = currentIndex
}
		// ...
function pageNotValid(pageNumber) {
    popupInfo.text = qsTr("Page %1 not valid.\nPlease tap 'Done' Button","").arg(pageNumber)
    popupInfo.buttonText = qsTr("So Long, and Thx For All The Fish")
    popupInfo.open()
}

popup_invalid

Please explore the sources – this blog article is only a snapshot.

Summary

This app is only using some of the powerful features SwipeView provides to you for navigation through some pages. Remember: this is not a production-ready app.

When to use a SwipeView and when to use a StackView ?

SwipeViews are great if you’re following a well-defined path from Page-to-Page as explained for a Wizard here.

StackView is great when user decides wich Page should be pushed on Top or removed from.

Using a StackView or SwipeView is not the only ways to navigate – stay tuned for the next Example App using a TabBar.


← Back (Stacked Pages App)

→ Next Article (Tab Bar App)

⇐ Home (Overview / Topics)

4 responses to Swiped Pages App

  1. 

    > Please notice: if clicked on FAB you must reset the Focus to use Shortcuts properly. This is new with 5.7 RC.

    Buttons and sliders now have a “strong” (tab & click) focus policy by default. To disable click focus, you can set either “focusPolicy: Qt.NoFocus” or “focusPolicy: Qt.TabFocus” depending whether you want to keep tab focus: http://doc-snapshots.qt.io/qt5-5.7/qml-qtquick-controls2-control.html#focusPolicy-prop

  2. 

    With Qt Quick Controls 2.1 in Qt 5.8, it will be easier to unload pages that are outside the reach:

    SwipeView {
    Repeater {
    model: 6
    Loader {
    active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem // <==
    sourceComponent: Text {
    text: index
    Component.onCompleted: console.log("created:", index)
    Component.onDestruction: console.log("destroyed:", index)
    }
    }
    }
    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s