| // 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 |