import Foundation import UIKit import SwiftSignalKit public final class ListViewReorderingGestureRecognizer: UIGestureRecognizer { private let shouldBegin: (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, itemNode: ListViewItemNode?) private let willBegin: (CGPoint) -> Void private let began: (ListViewItemNode) -> Void private let ended: () -> Void private let moved: (CGFloat) -> Void private var initialLocation: CGPoint? private var longTapTimer: SwiftSignalKit.Timer? private var longPressTimer: SwiftSignalKit.Timer? private var itemNode: ListViewItemNode? public init(shouldBegin: @escaping (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, itemNode: ListViewItemNode?), willBegin: @escaping (CGPoint) -> Void, began: @escaping (ListViewItemNode) -> Void, ended: @escaping () -> Void, moved: @escaping (CGFloat) -> Void) { self.shouldBegin = shouldBegin self.willBegin = willBegin self.began = began self.ended = ended self.moved = moved super.init(target: nil, action: nil) } deinit { self.longTapTimer?.invalidate() self.longPressTimer?.invalidate() } private func startLongTapTimer() { self.longTapTimer?.invalidate() let longTapTimer = SwiftSignalKit.Timer(timeout: 0.25, repeat: false, completion: { [weak self] in self?.longTapTimerFired() }, queue: Queue.mainQueue()) self.longTapTimer = longTapTimer longTapTimer.start() } private func stopLongTapTimer() { self.itemNode = nil self.longTapTimer?.invalidate() self.longTapTimer = nil } private func startLongPressTimer() { self.longPressTimer?.invalidate() let longPressTimer = SwiftSignalKit.Timer(timeout: 0.6, repeat: false, completion: { [weak self] in self?.longPressTimerFired() }, queue: Queue.mainQueue()) self.longPressTimer = longPressTimer longPressTimer.start() } private func stopLongPressTimer() { self.itemNode = nil self.longPressTimer?.invalidate() self.longPressTimer = nil } override public func reset() { super.reset() self.itemNode = nil self.stopLongTapTimer() self.stopLongPressTimer() self.initialLocation = nil } private func longTapTimerFired() { guard let location = self.initialLocation else { return } self.longTapTimer?.invalidate() self.longTapTimer = nil self.willBegin(location) } private func longPressTimerFired() { guard let _ = self.initialLocation else { return } self.state = .began self.longPressTimer?.invalidate() self.longPressTimer = nil self.longTapTimer?.invalidate() self.longTapTimer = nil if let itemNode = self.itemNode { self.began(itemNode) } } override public func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) if self.numberOfTouches > 1 { self.state = .failed self.ended() return } if self.state == .possible { if let location = touches.first?.location(in: self.view) { let (allowed, requiresLongPress, itemNode) = self.shouldBegin(location) if allowed { self.itemNode = itemNode self.initialLocation = location if requiresLongPress { self.startLongTapTimer() self.startLongPressTimer() } else { self.state = .began if let itemNode = self.itemNode { self.began(itemNode) } } } else { self.state = .failed } } else { self.state = .failed } } } override public func touchesEnded(_ touches: Set, with event: UIEvent) { super.touchesEnded(touches, with: event) self.initialLocation = nil self.stopLongTapTimer() if self.longPressTimer != nil { self.stopLongPressTimer() self.state = .failed } if self.state == .began || self.state == .changed { self.ended() self.state = .failed } } override public func touchesCancelled(_ touches: Set, with event: UIEvent) { super.touchesCancelled(touches, with: event) self.initialLocation = nil self.stopLongTapTimer() if self.longPressTimer != nil { self.stopLongPressTimer() self.state = .failed } if self.state == .began || self.state == .changed { self.ended() self.state = .failed } } override public func touchesMoved(_ touches: Set, with event: UIEvent) { super.touchesMoved(touches, with: event) if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) { self.state = .changed let offset = location.y - initialLocation.y self.moved(offset) } else if let touch = touches.first, let initialTapLocation = self.initialLocation, self.longPressTimer != nil { let touchLocation = touch.location(in: self.view) let dX = touchLocation.x - initialTapLocation.x let dY = touchLocation.y - initialTapLocation.y if dX * dX + dY * dY > 3.0 * 3.0 { self.stopLongTapTimer() self.stopLongPressTimer() self.initialLocation = nil self.state = .failed } } } }