// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/side_swipe/ui_bundled/side_swipe_gesture_recognizer.h"
#import <cmath>
#import "base/numerics/angle_conversions.h"
namespace {
// The absolute maximum swipe angle from `y = 0` for a swipe to begin.
const CGFloat kMaxSwipeYAngle = 65;
// The minimum distance between touches for a swipe to begin.
const CGFloat kDefaultMinSwipeXThreshold = 4;
} // namespace
@implementation SideSwipeGestureRecognizer {
// Expected direction of the swipe, based on starting point.
UISwipeGestureRecognizerDirection _direction;
}
@synthesize swipeEdge = _swipeEdge;
@synthesize direction = _direction;
@synthesize swipeOffset = _swipeOffset;
@synthesize swipeThreshold = _swipeThreshold;
@synthesize startPoint = _startPoint;
- (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action {
self = [super initWithTarget:target action:action];
if (self) {
self.swipeThreshold = kDefaultMinSwipeXThreshold;
}
return self;
}
// To quickly avoid interference with other gesture recognizers, fail
// immediately if the touches aren't at the edge of the touched view.
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
[super touchesBegan:touches withEvent:event];
// Don't interrupt gestures in progress.
if (self.state != UIGestureRecognizerStatePossible)
return;
UITouch* touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self.view];
_startPoint = CGPointZero;
if (_swipeEdge > 0) {
if (location.x > _swipeEdge &&
location.x < CGRectGetMaxX([self.view bounds]) - _swipeEdge) {
self.state = UIGestureRecognizerStateFailed;
} else {
if (location.x < _swipeEdge) {
_direction = UISwipeGestureRecognizerDirectionRight;
} else {
_direction = UISwipeGestureRecognizerDirectionLeft;
}
_startPoint = location;
}
} else {
_startPoint = location;
_direction = 0;
}
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
// Revert to normal pan gesture recognizer characteristics after state began.
if (self.state != UIGestureRecognizerStatePossible) {
[super touchesMoved:touches withEvent:event];
return;
}
// Only one touch.
if ([[event allTouches] count] > 1) {
self.state = UIGestureRecognizerStateFailed;
return;
}
// In iOS10, sometimes a PanGestureRecognizer will fire a touchesMoved even
// after touchesBegan sets its state to `UIGestureRecognizerStateFailed`.
// Somehow the state is re-set to UIGestureRecognizerStatePossible, and ends
// up in moved. Checking if `_startPoint` has been set is a secondary way to
// catch for failed gestures.
if (CGPointEqualToPoint(_startPoint, CGPointZero)) {
self.state = UIGestureRecognizerStateFailed;
return;
}
// Don't swipe at an angle greater than `kMaxSwipeYAngle`.
UITouch* touch = [[event allTouches] anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
CGFloat dy = currentPoint.y - _startPoint.y;
CGFloat dx = std::abs(currentPoint.x - _startPoint.x);
CGFloat degrees = std::fabs(base::RadToDeg(std::atan2(dy, dx)));
if (degrees > kMaxSwipeYAngle) {
self.state = UIGestureRecognizerStateFailed;
return;
}
// On devices that support force presses a -touchesMoved fires when `force`
// changes and not the location of the touch. Ignore these events.
if (currentPoint.x == _startPoint.x) {
self.state = UIGestureRecognizerStatePossible;
return;
}
// Don't recognize swipe in the wrong direction.
if ((_direction == UISwipeGestureRecognizerDirectionRight &&
currentPoint.x - _startPoint.x < 0) ||
(_direction == UISwipeGestureRecognizerDirectionLeft &&
currentPoint.x - _startPoint.x > 0)) {
self.state = UIGestureRecognizerStateFailed;
return;
}
if (_swipeEdge == 0 && _direction == 0) {
if (currentPoint.x > _startPoint.x) {
_direction = UISwipeGestureRecognizerDirectionRight;
} else {
_direction = UISwipeGestureRecognizerDirectionLeft;
}
}
// Begin recognizer after `self.swipeThreshold` distance swiped.
if (std::abs(currentPoint.x - _startPoint.x) > self.swipeThreshold) {
if (_direction == UISwipeGestureRecognizerDirectionRight) {
_swipeOffset = currentPoint.x;
} else {
_swipeOffset = -(CGRectGetMaxX([self.view bounds]) - currentPoint.x);
}
self.state = UIGestureRecognizerStateBegan;
return;
}
}
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
_startPoint = CGPointZero;
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
_startPoint = CGPointZero;
[super touchesCancelled:touches withEvent:event];
}
@end