feat(swipe): iOS swipe to go back

This commit is contained in:
Adam Bradley
2015-02-06 16:29:48 -06:00
parent c17812444f
commit 8ebde73d0b
14 changed files with 416 additions and 118 deletions

View File

@@ -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;

View File

@@ -239,6 +239,7 @@ function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $io
self.transition(enteringHeaderBar, leavingHeaderBar, viewData);
self.isInitialized = true;
navSwipeAttr('');
};
@@ -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() {
@@ -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();
};
@@ -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;

View File

@@ -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';
@@ -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;
@@ -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;
};
@@ -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);
};
@@ -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;
});
};
@@ -118,6 +134,7 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
navBarDelegate = transitionData.navBarDelegate;
var associatedNavBarCtrl = getAssociatedNavBarCtrl();
associatedNavBarCtrl && associatedNavBarCtrl.update(transitionData);
navSwipeAttr('');
}
};
@@ -152,6 +169,8 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
}
}
}
navSwipeAttr('');
};
@@ -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++) {

View File

@@ -45,7 +45,7 @@ function($scope,
}
);
if (!angular.isDefined(scrollViewOptions.bouncing)) {
if (!isDefined(scrollViewOptions.bouncing)) {
ionic.Platform.ready(function() {
if (scrollView.options) {
scrollView.options.bouncing = true;
@@ -76,22 +76,11 @@ function($scope,
$scope.$on('$destroy', function() {
deregisterInstance();
//Windows: make sure the scrollView.__cleanup exists before calling it
if (scrollView.__cleanup) {
scrollView.__cleanup();
}
scrollView && scrollView.__cleanup && scrollView.__cleanup();
ionic.off('resize', resize, $window);
$window.removeEventListener('resize', resize);
scrollViewOptions = null;
self._scrollViewOptions.el = null;
self._scrollViewOptions = null;
$element.off('scroll', scrollFunc);
$element = null;
self.$element = null;
element = null;
self.element = null;
self.scrollView = null;
scrollView = null;
scrollView = self.scrollView = scrollViewOptions = self._scrollViewOptions = scrollViewOptions.el = self._scrollViewOptions.el = $element = self.$element = element = null;
});
$timeout(function() {
@@ -99,11 +88,11 @@ function($scope,
});
self.getScrollView = function() {
return self.scrollView;
return scrollView;
};
self.getScrollPosition = function() {
return self.scrollView.getValues();
return scrollView.getValues();
};
self.resize = function() {
@@ -169,6 +158,11 @@ function($scope,
});
};
self.freezeScroll = function(shouldFreeze) {
if (arguments.length) scrollView.options.freeze = shouldFreeze;
return scrollView.options.freeze;
};
/**
* @private

View File

@@ -51,20 +51,14 @@ function($scope, $element, $attrs, $compile, $rootScope, $ionicViewSwitcher) {
navBarItems[n] = generateNavBarItem(navElementHtml[n]);
}
navViewCtrl.beforeEnter({
navViewCtrl.beforeEnter(extend(transData, {
title: viewTitle,
direction: transData.direction,
transition: transData.transition,
navBarTransition: transData.navBarTransition,
transitionId: transData.transitionId,
shouldAnimate: transData.shouldAnimate,
enableBack: transData.enableBack,
showBack: !attrTrue('hideBackButton'),
navBarItems: navBarItems,
navBarDelegate: navBarDelegateHandle || null,
showNavBar: !attrTrue('hideNavBar'),
hasHeaderBar: !!hasViewHeaderBar
});
}));
// make sure any existing observers are cleaned up
deregisterFns();