iOS 7 Style swipe navigation

Ok folks, I’d just like to let everyone know that I’ve completely rewritten my back-swipe navigation library. I have also included my own version of the template scene module.

I really, really have to thank @ingemar for the animation updates to the composer api because without that I would not have been inspired to go looking in the right places. Thank you @ingemar.

If you’ve not seen it yet, you can find the animations here: http://forums.coronalabs.com/topic/50598-ios-7-style-scene-transitions/?p=262138

You can find my complete back-swipe library included in my example app in the Code Exchange:

http://code.coronalabs.com/code/backswipe-navigation-composer

Looks and works great!!! Thanks much for sharing. 

@horacebury

I really, really have to thank @ingemar for the animation updates to the composer api because without that I would not have been inspired to go looking in the right places. Thank you @ingemar.

You’re welcome  :).
 
Your library is looking good. 
 
It seems like I’ll be a bit late to the party with my own library, however I’ll release it on GitHub as well once I’ve finalized it. The implementation is similar, but still a bit different.

@horacebury I’m trying this now in my own project. It really takes the app up a notch to have this functionality. I really appreciate your work on this.

I do have some buttons and stuff underneath the touch zone that stop functioning since the touch zone takes focus and also returns on touch.

For my use case it should not take focus until after finger has dragged 3px or so to confirm that is the users intention. So I guess I will be adding that.

The return true/false placements in the touch function look very specifically placed. I tried removing them and it did not seem to have any negative effect. Anything you can think of that would cause problem with that? 

The problem with tracking a touch for a given distance before taking focus is that everything has to return false during that period, which also means everything under the touch will continue to receive that touch event. You really have to approach it with a specific scenario piece of code.

What I did, though not in this code admittedly, was to take focus on the began phase and if the touch distance does not travel further than a given threshold to simply release the focus, change the event.phase value back to began and return false. This is not a perfect solution, but similar to handling swipes when using a scrollview…

You may want the scrollview to handle vertical swipes but only if the touch travels further vertically than it does horizontally. If it travels further horizontally, you might want to perform a row swipe to reveal a delete button or something. In this situation, what is recommended is to take focus immediately and then call takeFocus() on scrollview to pass the touch event to it. This approach could work if you know what is below the touch zone and you implement your own takeFocus() on the objects below.

The return true and return false statements in my touch() function are the standard way of handling touch events on a single object. In began we return true to tell the system we want to handle the start of the touch. There is a lot of setup there, too, to tell the object that it owns the touch. Moved and ended are handled only if the object has the touch focus. We return false at the end if the event is not relevant to the object because it does not have focus. This all allows an object to neatly capture touch/drag events without getting confused with other object’s touch events, which is especially useful in a multitouch environment, but not only.

TL;DR: Yes, they are specifically placed and removing them will cause problems. You probably want to fire off additional, custom functions in those phases rather than changing the functionality.

Thank you for such detailed answer! Very helpful.

I worked on this a bit more and you are right, without the returns things go awry.

I ended up doing it like this which works well if you don’t have scrollviews etc. All non dragging touches go through.

[lua]local startX

local touchPanelActive

local function touch(e)

    

    if not composer.backSwipeActive then 

        return false 

    end

    

    local phase = e.phase

    

    if  not touchPanelActive then

        if phase == “began” then

            startX = e.x

        elseif phase == “moved” and e.x - startX > 5 then

            print(“Touchpanel activated”)

            composer.getCurrentScene():dispatchEvent{ name=“swipe”, phase=“activated” }

            touchPanelActive = true

            touch({x = e.x, y = e.y, target = e.target, phase = “began”}) – Call function again with began phase but with activated touchpanel

        end

        

    else

        – Previous touch function unchanged except touchPanelActive = false on cancel and complete

[/lua]

One thing I would want to do is not to load the back scene that is underneath until old scene has been completely swept away. Right now it loads it as soon as you let go and in my case the scene underneath has to access db which means stuttering during the transition.

So I’m doing it like this at the moment:

[lua]-- Before dispatching ended phase, move view out of the way

transition.to( view, { time=50, x=display.contentWidth, onComplete=function()

    composer.getCurrentScene():dispatchEvent{ name=“swipe”, phase=“ended” }

end } )[/lua]

It looks a tiny bit odd, but its so fast that its hardly noticeable. There is probably a better way.

Hey,

I was about to try download the code to use the swipe feature to move in beetwen scene, but the links are down. Will it be availabe again soon?

In the meanwhile I was using this code:

local swipeLayer = display.newRect( centerX, centerY, display.contentWidth, display.contentHeight ) &nbsp;&nbsp;&nbsp; swipeLayer.alpha=0 --make it transparent &nbsp;&nbsp;&nbsp; swipeLayer.isHitTestable = true &nbsp;&nbsp;&nbsp; sceneGroup:insert( swipeLayer ) &nbsp;&nbsp;&nbsp; local function startDrag(event) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; local swipeLength = math.abs(event.x - event.xStart) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print(event.phase, swipeLength) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; local t = event.target &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; local phase = event.phase &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if "began" == phase then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return true &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elseif "moved" == phase then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elseif "ended" == phase or "cancelled" == phase then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if event.xStart \> event.x and swipeLength \> 50 then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print("Swiped Left") &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elseif event.xStart \< event.x and swipeLength \> 50 then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print( "Swiped Right" ) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; composer.gotoScene("counter", "slideRight") &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end &nbsp;&nbsp;&nbsp; end &nbsp;local function sensitizeLayer () &nbsp;&nbsp;&nbsp; swipeLayer:addEventListener("touch", startDrag) &nbsp; end &nbsp; timer.performWithDelay( 800, sensitizeLayer) 

…provided by a user in a former discussion on the same topic, but if I try to swipe back in mid transition app crashes. I also added a function at the bottom to make the layer sensitive to touch only after the transition has finished, so how is it possible that I can, at least begin, to swipe back and having the app crashed??

@akak It looks like the code share page is down at the moment. This url should work when it comes back: http://code.coronalabs.com/code/backswipe-navigation-composer

@akak: Here is a direct link: https://gist.github.com/HoraceBury/21c51048d717d49c3f29

@jonjonsson are you looking to swipe backwards iOS7 style or just swipe the current display off-screen?

If you want iOS7 style you need to have the previous scene loaded anyway. If you just want to swipe objects, you can use the touch function I provided as is.

If you want tap events to drop through an object but you want that object to catch touch events, just listen for tap events on the object below and not on the top object.

If you want touch events which move within a certain threshold distance and then drop through to the object below once the threshold is met, you want to be using the method I mentioned of releasing the focus and changing the phase of the event, then returning false. This should effectively cause a “pass-through” of the touch even after it has been “acquired” by the top object.

It’s a bit complex at times and the simplest solution is to code for your particular situation. I’ve tried writing generic touch/gesture handling libraries a number of times and it’s generally not practically because of the huge range of situations. And that’s without multi-touch enabled.

Hey @horacebury,

Great job with this library! I am looking for a way to change scene by swiping anywhere on the screen and get a ‘slide’ transition between scenes. I only have a few button in each scene to be tapped, that’s it for in-scene interactive elements.

How would you suggest to implement your work to get this result? Being new at programming I’d need some help to figure out where to put the files/code you provided.

Thanks a lot for the amazing resource you’re offering.

I’m using iOS7 style, the db is refreshed on show event “will” so the back swipe transition is disturbed. I can move it to “did” and it should be fine.

I don’t like to use tap in general (highlighting on begin phase etc) so my modification is an easy fix for my use case and allows touches with no problem.

@horacebury I’m trying this now in my own project. It really takes the app up a notch to have this functionality. I really appreciate your work on this.

I do have some buttons and stuff underneath the touch zone that stop functioning since the touch zone takes focus and also returns on touch.

For my use case it should not take focus until after finger has dragged 3px or so to confirm that is the users intention. So I guess I will be adding that.

The return true/false placements in the touch function look very specifically placed. I tried removing them and it did not seem to have any negative effect. Anything you can think of that would cause problem with that? 

The problem with tracking a touch for a given distance before taking focus is that everything has to return false during that period, which also means everything under the touch will continue to receive that touch event. You really have to approach it with a specific scenario piece of code.

What I did, though not in this code admittedly, was to take focus on the began phase and if the touch distance does not travel further than a given threshold to simply release the focus, change the event.phase value back to began and return false. This is not a perfect solution, but similar to handling swipes when using a scrollview…

You may want the scrollview to handle vertical swipes but only if the touch travels further vertically than it does horizontally. If it travels further horizontally, you might want to perform a row swipe to reveal a delete button or something. In this situation, what is recommended is to take focus immediately and then call takeFocus() on scrollview to pass the touch event to it. This approach could work if you know what is below the touch zone and you implement your own takeFocus() on the objects below.

The return true and return false statements in my touch() function are the standard way of handling touch events on a single object. In began we return true to tell the system we want to handle the start of the touch. There is a lot of setup there, too, to tell the object that it owns the touch. Moved and ended are handled only if the object has the touch focus. We return false at the end if the event is not relevant to the object because it does not have focus. This all allows an object to neatly capture touch/drag events without getting confused with other object’s touch events, which is especially useful in a multitouch environment, but not only.

TL;DR: Yes, they are specifically placed and removing them will cause problems. You probably want to fire off additional, custom functions in those phases rather than changing the functionality.

Thank you for such detailed answer! Very helpful.

I worked on this a bit more and you are right, without the returns things go awry.

I ended up doing it like this which works well if you don’t have scrollviews etc. All non dragging touches go through.

[lua]local startX

local touchPanelActive

local function touch(e)

    

    if not composer.backSwipeActive then 

        return false 

    end

    

    local phase = e.phase

    

    if  not touchPanelActive then

        if phase == “began” then

            startX = e.x

        elseif phase == “moved” and e.x - startX > 5 then

            print(“Touchpanel activated”)

            composer.getCurrentScene():dispatchEvent{ name=“swipe”, phase=“activated” }

            touchPanelActive = true

            touch({x = e.x, y = e.y, target = e.target, phase = “began”}) – Call function again with began phase but with activated touchpanel

        end

        

    else

        – Previous touch function unchanged except touchPanelActive = false on cancel and complete

[/lua]

One thing I would want to do is not to load the back scene that is underneath until old scene has been completely swept away. Right now it loads it as soon as you let go and in my case the scene underneath has to access db which means stuttering during the transition.

So I’m doing it like this at the moment:

[lua]-- Before dispatching ended phase, move view out of the way

transition.to( view, { time=50, x=display.contentWidth, onComplete=function()

    composer.getCurrentScene():dispatchEvent{ name=“swipe”, phase=“ended” }

end } )[/lua]

It looks a tiny bit odd, but its so fast that its hardly noticeable. There is probably a better way.

Hey,

I was about to try download the code to use the swipe feature to move in beetwen scene, but the links are down. Will it be availabe again soon?

In the meanwhile I was using this code:

local swipeLayer = display.newRect( centerX, centerY, display.contentWidth, display.contentHeight ) &nbsp;&nbsp;&nbsp; swipeLayer.alpha=0 --make it transparent &nbsp;&nbsp;&nbsp; swipeLayer.isHitTestable = true &nbsp;&nbsp;&nbsp; sceneGroup:insert( swipeLayer ) &nbsp;&nbsp;&nbsp; local function startDrag(event) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; local swipeLength = math.abs(event.x - event.xStart) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print(event.phase, swipeLength) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; local t = event.target &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; local phase = event.phase &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if "began" == phase then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return true &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elseif "moved" == phase then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elseif "ended" == phase or "cancelled" == phase then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if event.xStart \> event.x and swipeLength \> 50 then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print("Swiped Left") &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; elseif event.xStart \< event.x and swipeLength \> 50 then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print( "Swiped Right" ) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; composer.gotoScene("counter", "slideRight") &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end &nbsp;&nbsp;&nbsp; end &nbsp;local function sensitizeLayer () &nbsp;&nbsp;&nbsp; swipeLayer:addEventListener("touch", startDrag) &nbsp; end &nbsp; timer.performWithDelay( 800, sensitizeLayer) 

…provided by a user in a former discussion on the same topic, but if I try to swipe back in mid transition app crashes. I also added a function at the bottom to make the layer sensitive to touch only after the transition has finished, so how is it possible that I can, at least begin, to swipe back and having the app crashed??

@akak It looks like the code share page is down at the moment. This url should work when it comes back: http://code.coronalabs.com/code/backswipe-navigation-composer

@akak: Here is a direct link: https://gist.github.com/HoraceBury/21c51048d717d49c3f29