Skip to content

Commit

Permalink
feat(swipe): iOS swipe to go back
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdbradley committed Feb 6, 2015
1 parent c178124 commit 8ebde73
Show file tree
Hide file tree
Showing 14 changed files with 416 additions and 118 deletions.
5 changes: 3 additions & 2 deletions js/angular/controller/headerBarController.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,14 @@ function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) {
};


self.resetBackButton = function() {
self.resetBackButton = function(viewData) {
if ($ionicConfig.backButton.previousTitleText()) {
var previousTitleEle = getEle(PREVIOUS_TITLE);
if (previousTitleEle) {
previousTitleEle.classList.remove(HIDE);

var newPreviousTitleText = $ionicHistory.backTitle();
var view = (viewData && $ionicHistory.getViewById(viewData.viewId));
var newPreviousTitleText = $ionicHistory.backTitle(view);

if (newPreviousTitleText !== previousTitleText) {
previousTitleText = previousTitleEle.innerHTML = newPreviousTitleText;
Expand Down
54 changes: 42 additions & 12 deletions js/angular/controller/navBarController.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io
self.transition(enteringHeaderBar, leavingHeaderBar, viewData);

self.isInitialized = true;
navSwipeAttr('');
};


Expand All @@ -254,17 +255,39 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io
ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', viewData.navBarTransition);
ionic.DomUtil.cachedAttr($element, 'nav-bar-direction', viewData.direction);

if (navBarTransition.shouldAnimate) {
if (navBarTransition.shouldAnimate && viewData.renderEnd) {
navBarAttr(enteringHeaderBar, 'stage');
} else {
navBarAttr(enteringHeaderBar, 'entering');
navBarAttr(leavingHeaderBar, 'leaving');
}

enteringHeaderBarCtrl.resetBackButton();
enteringHeaderBarCtrl.resetBackButton(viewData);

navBarTransition.run(0);

self.activeTransition = {
run: function(step) {
navBarTransition.shouldAnimate = false;
navBarTransition.direction = 'back';
navBarTransition.run(step);
},
cancel: function(shouldAnimate, speed) {
navSwipeAttr(speed);
navBarAttr(leavingHeaderBar, 'active');
navBarAttr(enteringHeaderBar, 'cached');
navBarTransition.shouldAnimate = shouldAnimate;
navBarTransition.run(0);
self.activeTransition = navBarTransition = null;
},
complete: function(shouldAnimate, speed) {
navSwipeAttr(speed);
navBarTransition.shouldAnimate = shouldAnimate;
navBarTransition.run(1);
queuedTransitionEnd = transitionEnd;
}
};

$timeout(enteringHeaderBarCtrl.align, 16);

queuedTransitionStart = function() {
Expand All @@ -277,23 +300,26 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io

queuedTransitionEnd = function() {
if (latestTransitionId == transitionId || !navBarTransition.shouldAnimate) {
for (var x = 0; x < headerBars.length; x++) {
headerBars[x].isActive = false;
}
enteringHeaderBar.isActive = true;

navBarAttr(enteringHeaderBar, 'active');
navBarAttr(leavingHeaderBar, 'cached');

queuedTransitionEnd = null;
transitionEnd();
}
};

queuedTransitionStart = null;
};

queuedTransitionStart();
function transitionEnd() {
for (var x = 0; x < headerBars.length; x++) {
headerBars[x].isActive = false;
}
enteringHeaderBar.isActive = true;

navBarAttr(enteringHeaderBar, 'active');
navBarAttr(leavingHeaderBar, 'cached');

self.activeTransition = navBarTransition = queuedTransitionEnd = null;
}

queuedTransitionStart();
};


Expand Down Expand Up @@ -437,6 +463,10 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io
ctrl && ionic.DomUtil.cachedAttr(ctrl.containerEle(), 'nav-bar', val);
}

function navSwipeAttr(val) {
ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
}


$scope.$on('$destroy', function() {
$scope.$parent.$hasHeader = false;
Expand Down
169 changes: 162 additions & 7 deletions js/angular/controller/navViewController.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ IonicModule
'$ionicNavViewDelegate',
'$ionicHistory',
'$ionicViewSwitcher',
function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher) {
'$ionicConfig',
'$ionicScrollDelegate',
function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher, $ionicConfig, $ionicScrollDelegate) {

var DATA_ELE_IDENTIFIER = '$eleId';
var DATA_DESTROY_ELE = '$destroyEle';
Expand All @@ -23,6 +25,8 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
var navBarDelegate;
var activeEleId;
var navViewAttr = $ionicViewSwitcher.navViewAttr;
var disableRenderStartViewId, disableAnimation;
var transitionDuration, transitionTiming;

self.scope = $scope;

Expand All @@ -44,6 +48,12 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,

$scope.$on('$ionicHistory.deselect', self.cacheCleanup);

ionic.Platform.ready(function() {
if (ionic.Platform.isWebView() && $ionicConfig.views.swipeBackEnabled()) {
self.initSwipeBack();
}
});

return viewData;
};

Expand All @@ -58,7 +68,10 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
self.update(registerData);

// begin rendering and transitioning
self.render(registerData, viewLocals, leavingView);
var enteringView = $ionicHistory.getViewById(registerData.viewId) || {};

var renderStart = (disableRenderStartViewId !== registerData.viewId);
self.render(registerData, viewLocals, enteringView, leavingView, renderStart, true);
};


Expand Down Expand Up @@ -94,19 +107,22 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
};


self.render = function(registerData, viewLocals, leavingView) {
var enteringView = $ionicHistory.getViewById(registerData.viewId) || {};

self.render = function(registerData, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
// register the view and figure out where it lives in the various
// histories and nav stacks, along with how views should enter/leave
var switcher = $ionicViewSwitcher.create(self, viewLocals, enteringView, leavingView);
var switcher = $ionicViewSwitcher.create(self, viewLocals, enteringView, leavingView, renderStart, renderEnd);

// init the rendering of views for this navView directive
switcher.init(registerData, function() {
// the view is now compiled, in the dom and linked, now lets transition the views.
// this uses a callback incase THIS nav-view has a nested nav-view, and after the NESTED
// nav-view links, the NESTED nav-view would update which direction THIS nav-view should use
switcher.transition(self.direction(), registerData.enableBack);

// kick off the transition of views
switcher.transition(self.direction(), registerData.enableBack, !disableAnimation);

// reset private vars for next time
disableRenderStartViewId = disableAnimation = null;
});

};
Expand All @@ -118,6 +134,7 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
navBarDelegate = transitionData.navBarDelegate;
var associatedNavBarCtrl = getAssociatedNavBarCtrl();
associatedNavBarCtrl && associatedNavBarCtrl.update(transitionData);
navSwipeAttr('');
}
};

Expand Down Expand Up @@ -152,6 +169,8 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
}
}
}

navSwipeAttr('');
};


Expand Down Expand Up @@ -261,6 +280,142 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
};


self.initSwipeBack = function() {
var swipeBackHitWidth = $ionicConfig.views.swipeBackHitWidth();
var viewTransition, associatedNavBarCtrl, backView;
var deregDragStart, deregDrag, deregRelease;
var windowWidth, startDragX, dragPoints;

function onDragStart(ev) {
if (!isPrimary) return;

startDragX = getDragX(ev);
if (startDragX > swipeBackHitWidth) return;

backView = $ionicHistory.backView();

if (!backView || backView.historyId !== $ionicHistory.currentView().historyId) return;

if (!windowWidth) windowWidth = window.innerWidth;

freezeScrolls(true);

var registerData = {
direction: 'back'
};

dragPoints = [];

var switcher = $ionicViewSwitcher.create(self, registerData, backView, $ionicHistory.currentView(), true, false);
switcher.loadViewElements(registerData);
switcher.render(registerData);

viewTransition = switcher.transition('back', $ionicHistory.enabledBack(backView), true);

associatedNavBarCtrl = getAssociatedNavBarCtrl();

deregDrag = ionic.onGesture('drag', onDrag, $element[0]);
deregRelease = ionic.onGesture('release', onRelease, $element[0]);
}

function onDrag(ev) {
if (isPrimary && viewTransition) {
var dragX = getDragX(ev);

dragPoints.push({
t: Date.now(),
x: dragX
});

if (dragX >= windowWidth - 15) {
onRelease(ev);

} else {
var step = Math.min(Math.max(getSwipeCompletion(dragX), 0), 1);
viewTransition.run(step);
associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.run(step);
}

}
}

function onRelease(ev) {
if (isPrimary && viewTransition && dragPoints && dragPoints.length > 1) {

var now = Date.now();
var releaseX = getDragX(ev);
var startDrag = dragPoints[dragPoints.length - 1];

for (var x = dragPoints.length - 2; x >= 0; x--) {
if (now - startDrag.t > 200) {
break;
}
startDrag = dragPoints[x];
}

var isSwipingRight = (releaseX >= dragPoints[dragPoints.length - 2].x);
var releaseSwipeCompletion = getSwipeCompletion(releaseX);
var velocity = Math.abs(startDrag.x - releaseX) / (now - startDrag.t);

// private variables because ui-router has no way to pass custom data using $state.go
disableRenderStartViewId = backView.viewId;
disableAnimation = (releaseSwipeCompletion < 0.03 || releaseSwipeCompletion > 0.97);

if (isSwipingRight && (releaseSwipeCompletion > 0.5 || velocity > 0.1)) {
var speed = (velocity > 0.5 || velocity < 0.05 || releaseX > windowWidth - 45) ? 'fast' : 'slow';
navSwipeAttr(disableAnimation ? '' : speed);
backView.go();
associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.complete(!disableAnimation, speed);

} else {
navSwipeAttr(disableAnimation ? '' : 'fast');
disableRenderStartViewId = null;
viewTransition.cancel(!disableAnimation);
associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.cancel(!disableAnimation, 'fast');
disableAnimation = null;
}

}

ionic.offGesture(deregDrag, 'drag', onDrag);
ionic.offGesture(deregRelease, 'release', onRelease);

windowWidth = viewTransition = dragPoints = null;

freezeScrolls(false);
}

function getDragX(ev) {
return ionic.tap.pointerCoord(ev.gesture.srcEvent).x;
}

function getSwipeCompletion(dragX) {
return (dragX - startDragX) / windowWidth;
}

deregDragStart = ionic.onGesture('dragstart', onDragStart, $element[0]);

$scope.$on('$destroy', function() {
ionic.offGesture(deregDragStart, 'dragstart', onDragStart);
ionic.offGesture(deregDrag, 'drag', onDrag);
ionic.offGesture(deregRelease, 'release', onRelease);
viewTransition = associatedNavBarCtrl = null;
});
};


function freezeScrolls(freeze) {
forEach($ionicScrollDelegate._instances, function(instance) {
instance.freezeScroll(freeze);
});
}


function navSwipeAttr(val) {
ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
}


function getAssociatedNavBarCtrl() {
if (navBarDelegate) {
for (var x=0; x < $ionicNavBarDelegate._instances.length; x++) {
Expand Down
Loading

17 comments on commit 8ebde73

@lanceli
Copy link

@lanceli lanceli commented on 8ebde73 Feb 9, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome!

@asterism612
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to apply?

@lanceli
Copy link

@lanceli lanceli commented on 8ebde73 Feb 9, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asterism612 just update ionic to latest nightly version

 "ionic": "driftyco/ionic-bower#v1.0.0-beta.14-nightly-997"

@asterism612
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nightly-998 is not work...

@lanceli
Copy link

@lanceli lanceli commented on 8ebde73 Feb 9, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asterism612 it works on my iOS simulator, not works in browser.

@asterism612
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i run on device;

@lanceli
Copy link

@lanceli lanceli commented on 8ebde73 Feb 9, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works for me on my iPhone 5

@gastonbesada
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamdbradley It's not working for me with 1.0.0-beta.14-nightly-998 freeze the app on device iOS 8.1.3 when swipe to back. On browser I'm getting:

 TypeError: Cannot read property 'removeEventListener' of undefined
    at Object.ionic.EventController.off (ionic.bundle.js:550)
    at Object.ionic.off (ionic.bundle.js:599)
    at destroy (ionic.bundle.js:48187)
    at Scope.$get.Scope.$broadcast (ionic.bundle.js:22591)
    at Scope.$get.Scope.$destroy (ionic.bundle.js:22211)
    at destroyViewEle (ionic.bundle.js:46375)
    at Object.IonicModule.factory.ionicViewSwitcher.create.switcher.cleanup (ionic.bundle.js:46241)
    at HTMLDivElement.transitionComplete (ionic.bundle.js:46175)
    at HTMLDivElement.eventHandler (ionic.bundle.js:10895)

@argyleink
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works in Ionic View for iOS, but not in Safari. So I assume this is works in a Cordova built app as well. Is this by design? I'd really like this feature in the browser (standalone "add to homescreen") style as well, not just when in a Cordova webview.. Makes sense not to have this feature in browser, since Safari has it's own edge swipe to go back gesture.

@bensperry
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@argyleink Yes, it's purposefully turned off for Safari because of that reason. You can manually override but I wouldn't recommend it.

@argyleink
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh sweet, so i could manually override if in a "standalone" environment? lovely! i'll scour the docs, unless you can quick link me to it? Thanks for the response mang!

@argyleink
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this what I need?

$ionicConfigProvider.views.swipeBackEnabled(true);

@kinhunt
Copy link

@kinhunt kinhunt commented on 8ebde73 Mar 9, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried on iOS 8.1.2 (iphone5) and iOS 8.1.3 (iphone6), it doesn't seem to be working, with or without $ionicConfigProvider.views.swipeBackEnabled(true);

@argyleink
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't get it to work either. Function seems ineffective when setting it to true.

@adamdbradley
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is only going to work on iOS Cordova, so swipeBackEnabled is only effective on iOS cordova.

@zaynetro
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to disable it? $ionicConfigProvider.views.swipeBackEnabled(false); doesn't seem to work

@johnmiroki
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamdbradley even if I $ionicConfigProvider.views.swipeBackEnabled(false); in app.config, I can still swipe back in a certain views. Any suggestion?

Please sign in to comment.