import Foundation import UIKit import AsyncDisplayKit import Display public enum ContextGestureTransition { case begin case update case ended(CGFloat) } private class TimerTargetWrapper: NSObject { let f: () -> Void init(_ f: @escaping () -> Void) { self.f = f } @objc func timerEvent() { self.f() } } private let beginDelay: Double = 0.1 private func cancelParentGestures(view: UIView) { if let gestureRecognizers = view.gestureRecognizers { for recognizer in gestureRecognizers { recognizer.state = .failed } } if let node = (view as? ListViewBackingView)?.target { node.cancelSelection() } if let superview = view.superview { cancelParentGestures(view: superview) } } public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDelegate { private var currentProgress: CGFloat = 0.0 private var delayTimer: Timer? private var animator: DisplayLinkAnimator? private var isValidated: Bool = false public var activationProgress: ((CGFloat, ContextGestureTransition) -> Void)? public var activated: ((ContextGesture) -> Void)? public var externalUpdated: ((UIView?, CGPoint) -> Void)? public var externalEnded: (((UIView?, CGPoint)?) -> Void)? override public init(target: Any?, action: Selector?) { super.init(target: target, action: action) self.delegate = self } override public func reset() { super.reset() self.endPressedAppearance() self.currentProgress = 0.0 self.delayTimer?.invalidate() self.delayTimer = nil self.isValidated = false self.externalUpdated = nil self.externalEnded = nil self.animator?.invalidate() self.animator = nil } public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { if otherGestureRecognizer is UIPanGestureRecognizer { return false } return true } override public func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) if self.delayTimer == nil { let delayTimer = Timer(timeInterval: beginDelay, target: TimerTargetWrapper { [weak self] in guard let strongSelf = self, let _ = strongSelf.delayTimer else { return } strongSelf.isValidated = true if strongSelf.animator == nil { strongSelf.animator = DisplayLinkAnimator(duration: 0.3, from: 0.0, to: 1.0, update: { value in guard let strongSelf = self else { return } if strongSelf.isValidated { strongSelf.currentProgress = value strongSelf.activationProgress?(value, .update) } }, completion: { guard let strongSelf = self else { return } switch strongSelf.state { case .possible: strongSelf.delayTimer?.invalidate() strongSelf.animator?.invalidate() strongSelf.activated?(strongSelf) if let view = strongSelf.view?.superview { cancelParentGestures(view: view) } strongSelf.state = .began default: break } }) } strongSelf.activationProgress?(strongSelf.currentProgress, .begin) }, selector: #selector(TimerTargetWrapper.timerEvent), userInfo: nil, repeats: false) self.delayTimer = delayTimer RunLoop.main.add(delayTimer, forMode: .common) } } override public func touchesMoved(_ touches: Set, with event: UIEvent) { super.touchesMoved(touches, with: event) if let touch = touches.first { /*if #available(iOS 9.0, *) { let maxForce: CGFloat = max(2.5, min(3.0, touch.maximumPossibleForce)) let progress = touch.force / maxForce self.currentProgress = progress if self.isValidated { self.activationProgress?(progress, .update) } if touch.force >= maxForce { switch self.state { case .possible: self.delayTimer?.invalidate() self.activated?(self) if let view = self.view?.superview { cancelParentGestures(view: view) } self.state = .began default: break } } }*/ self.externalUpdated?(self.view, touch.location(in: self.view)) } } override public func touchesEnded(_ touches: Set, with event: UIEvent) { super.touchesEnded(touches, with: event) if let touch = touches.first { if !self.currentProgress.isZero, self.isValidated { self.currentProgress = 0.0 self.activationProgress?(0.0, .ended(self.currentProgress)) } self.externalEnded?((self.view, touch.location(in: self.view))) } self.delayTimer?.invalidate() self.animator?.invalidate() self.state = .failed } override public func touchesCancelled(_ touches: Set, with event: UIEvent) { super.touchesCancelled(touches, with: event) if let touch = touches.first, !self.currentProgress.isZero, self.isValidated { self.currentProgress = 0.0 self.activationProgress?(0.0, .ended(self.currentProgress)) } self.delayTimer?.invalidate() self.animator?.invalidate() self.state = .failed } public func cancel() { if !self.currentProgress.isZero, self.isValidated { self.currentProgress = 0.0 self.activationProgress?(0.0, .ended(self.currentProgress)) self.delayTimer?.invalidate() self.animator?.invalidate() self.state = .failed } } public func endPressedAppearance() { if !self.currentProgress.isZero, self.isValidated { let previousProgress = self.currentProgress self.currentProgress = 0.0 self.delayTimer?.invalidate() self.animator?.invalidate() self.isValidated = false self.activationProgress?(0.0, .ended(previousProgress)) } } }