blob: 670b4020266bdc42861c395b43c89e8cf7013c7f [file] [log] [blame] [edit]
// Copyright 2015-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "MDCInkTouchController.h"
#import "MDCInkGestureRecognizer.h"
#import "MDCInkTouchControllerDelegate.h"
#import "MDCInkView.h"
static const NSTimeInterval kInkTouchDelayInterval = 0.1;
@interface MDCInkTouchController ()
@property(nonatomic, strong) MDCInkView *addedInkView;
@property(nonatomic, strong) MDCInkView *defaultInkView;
@property(nonatomic, assign) BOOL shouldRespondToTouch;
@property(nonatomic, assign) CGPoint previousLocation;
@end
@implementation MDCInkTouchController
- (CGFloat)dragCancelDistance {
return _gestureRecognizer.dragCancelDistance;
}
- (void)setDragCancelDistance:(CGFloat)dragCancelDistance {
_gestureRecognizer.dragCancelDistance = dragCancelDistance;
}
- (BOOL)cancelsOnDragOut {
return _gestureRecognizer.cancelOnDragOut;
}
- (void)setCancelsOnDragOut:(BOOL)cancelsOnDragOut {
_gestureRecognizer.cancelOnDragOut = cancelsOnDragOut;
}
- (CGRect)targetBounds {
return _gestureRecognizer.targetBounds;
}
- (void)setTargetBounds:(CGRect)targetBounds {
_gestureRecognizer.targetBounds = targetBounds;
}
- (instancetype)initWithView:(UIView *)view {
self = [super init];
if (self) {
_requiresFailureOfScrollViewGestures = NO;
_gestureRecognizer =
[[MDCInkGestureRecognizer alloc] initWithTarget:self action:@selector(handleInkGesture:)];
_gestureRecognizer.delegate = self;
_view = view;
[_view addGestureRecognizer:_gestureRecognizer];
_defaultInkView = [[MDCInkView alloc] initWithFrame:view.bounds];
_defaultInkView.inkColor = _defaultInkView.defaultInkColor;
_defaultInkView.autoresizingMask =
UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
}
return self;
}
- (void)dealloc {
[_view removeGestureRecognizer:_gestureRecognizer];
_gestureRecognizer.delegate = nil;
}
- (void)addInkView {
if (![_delegate respondsToSelector:@selector(inkTouchController:inkViewAtTouchLocation:)]) {
_addedInkView = _defaultInkView;
if ([_delegate respondsToSelector:@selector(inkTouchController:insertInkView:intoView:)]) {
[_delegate inkTouchController:self insertInkView:_addedInkView intoView:_view];
} else {
[_view addSubview:_addedInkView];
}
}
}
- (void)cancelInkTouchProcessing {
[_addedInkView cancelAllAnimationsAnimated:YES];
}
- (MDCInkView *_Nullable)inkViewAtTouchLocation:(CGPoint)location {
MDCInkView *inkView;
if ([_delegate respondsToSelector:@selector(inkTouchController:inkViewAtTouchLocation:)]) {
inkView = [_delegate inkTouchController:self inkViewAtTouchLocation:location];
} else {
CGPoint locationInInkCoords = [self.view convertPoint:location toView:_addedInkView];
if ([_addedInkView pointInside:locationInInkCoords withEvent:nil]) {
inkView = _addedInkView;
}
}
return inkView;
}
- (void)handleInkGesture:(MDCInkGestureRecognizer *)recognizer {
CGPoint touchLocation = [recognizer locationInView:_view];
switch (recognizer.state) {
case UIGestureRecognizerStateBegan: {
if ([_delegate respondsToSelector:@selector(inkTouchController:inkViewAtTouchLocation:)]) {
_addedInkView = [_delegate inkTouchController:self inkViewAtTouchLocation:touchLocation];
if (!_addedInkView) {
return [self cancelInkGestureWithRecognizer:recognizer];
}
NSAssert([_addedInkView isDescendantOfView:_view],
@"Ink view %@ returned by inkTouchController:inkViewAtTouchLocation: must be a "
"subview of base view %@",
_addedInkView, _view);
recognizer.targetBounds = [_addedInkView convertRect:_addedInkView.bounds toView:_view];
}
_shouldRespondToTouch = YES;
dispatch_time_t delayTime =
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC * kInkTouchDelayInterval));
dispatch_after(_delaysInkSpread ? delayTime : 0, dispatch_get_main_queue(), ^(void) {
[self touchBeganAtPoint:[recognizer locationInView:self.addedInkView]
touchLocation:touchLocation];
});
break;
}
case UIGestureRecognizerStatePossible: // Ignored
break;
case UIGestureRecognizerStateChanged: {
// Due to changes on iPhone 6s, possibly due to the force touch,
// @c UIGestureRecognizerStateChanged constantly fires. However, we do not want to cancel the
// ink unless the users moves.
if (_shouldRespondToTouch && !CGPointEqualToPoint(touchLocation, _previousLocation)) {
_shouldRespondToTouch = NO;
}
break;
}
case UIGestureRecognizerStateCancelled:
[_addedInkView cancelAllAnimationsAnimated:YES];
_shouldRespondToTouch = NO;
break;
case UIGestureRecognizerStateRecognized:
[_addedInkView startTouchEndedAnimationAtPoint:touchLocation completion:nil];
_shouldRespondToTouch = NO;
break;
case UIGestureRecognizerStateFailed:
[_addedInkView cancelAllAnimationsAnimated:YES];
_shouldRespondToTouch = NO;
break;
}
if (_shouldRespondToTouch) {
_previousLocation = touchLocation;
} else {
_previousLocation = CGPointZero;
}
}
- (void)cancelInkGestureWithRecognizer:(MDCInkGestureRecognizer *)recognizer {
// To exit, disable the recognizer immediately which forces it to drop out of the current
// loop and prevent any state updates. Then re-enable to allow future gesture recognition.
recognizer.enabled = NO;
recognizer.enabled = YES;
}
- (void)touchBeganAtPoint:(CGPoint)point touchLocation:(CGPoint)touchLocation {
if (_shouldRespondToTouch) {
[_addedInkView startTouchBeganAnimationAtPoint:point completion:nil];
if ([_delegate respondsToSelector:@selector(inkTouchController:
didProcessInkView:atTouchLocation:)]) {
[_delegate inkTouchController:self
didProcessInkView:_addedInkView
atTouchLocation:touchLocation];
}
_shouldRespondToTouch = NO;
}
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(__unused UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(__unused UIGestureRecognizer *)other {
// Subclasses can override this to prioritize another recognizer.
return YES;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([_delegate respondsToSelector:@selector(inkTouchController:
shouldProcessInkTouchesAtTouchLocation:)]) {
CGPoint touchLocation = [gestureRecognizer locationInView:_view];
return [_delegate inkTouchController:self shouldProcessInkTouchesAtTouchLocation:touchLocation];
}
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (self.requiresFailureOfScrollViewGestures &&
[otherGestureRecognizer.view isKindOfClass:[UIScrollView class]] &&
![otherGestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] &&
![otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
return YES;
}
return NO;
}
#pragma mark - Deprecations
- (MDCInkView *)inkView {
return _defaultInkView;
}
@end