Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #284: Support Multi-tap and Long Press Gestures #288

Merged
merged 1 commit into from
Mar 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Source/model/scene/Group.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ open class Group: Node {

return shouldCheck
}

override func shouldCheckForLongTap() -> Bool {
var shouldCheck = super.shouldCheckForLongTap()
contents.forEach { node in
shouldCheck = shouldCheck || node.shouldCheckForLongTap()
}

return shouldCheck
}

override func shouldCheckForPan() -> Bool {
var shouldCheck = super.shouldCheckForPan()
Expand Down
92 changes: 85 additions & 7 deletions Source/model/scene/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ open class Node: Drawable {
var touchPressedHandlers = [ChangeHandler<TouchEvent>]()
var touchMovedHandlers = [ChangeHandler<TouchEvent>]()
var touchReleasedHandlers = [ChangeHandler<TouchEvent>]()

var prevTouchCount: Int = 0
var prevTouchTimer: Timer?
var isLongTapInProgress = false

var tapHandlers = [ChangeHandler<TapEvent>]()
var tapHandlers = [Int : [ChangeHandler<TapEvent>]]()
var longTapHandlers = [ChangeHandler<TapEvent>]()
var panHandlers = [ChangeHandler<PanEvent>]()
var rotateHandlers = [ChangeHandler<RotateEvent>]()
var pinchHandlers = [ChangeHandler<PinchEvent>]()
Expand Down Expand Up @@ -109,16 +114,33 @@ open class Node: Drawable {
}
}

@discardableResult public func onTap(_ f: @escaping (TapEvent) -> Void) -> Disposable {
@discardableResult public func onTap(tapCount: Int = 1, f: @escaping (TapEvent) -> Void) -> Disposable {
let handler = ChangeHandler<TapEvent>(f)
tapHandlers.append(handler)
if var handlers = tapHandlers[tapCount] {
handlers.append(handler)
} else {
tapHandlers[tapCount] = [handler]
}

return Disposable { [weak self] in
guard let index = self?.tapHandlers.index(of: handler) else {
guard let index = self?.tapHandlers[tapCount]?.index(of: handler) else {
return
}

self?.tapHandlers.remove(at: index)
self?.tapHandlers[tapCount]?.remove(at: index)
}
}

@discardableResult public func onLongTap(_ f: @escaping (TapEvent) -> Void) -> Disposable {
let handler = ChangeHandler<TapEvent>(f)
longTapHandlers.append(handler)

return Disposable { [weak self] in
guard let index = self?.longTapHandlers.index(of: handler) else {
return
}

self?.longTapHandlers.remove(at: index)
}
}

Expand Down Expand Up @@ -174,9 +196,61 @@ open class Node: Drawable {
func handleTouchMoved(_ event: TouchEvent) {
touchMovedHandlers.forEach { handler in handler.handle(event) }
}


// MARK: - Multiple tap handling

func handleTap( _ event: TapEvent ) {
tapHandlers.forEach { handler in handler.handle(event) }
if isLongTapInProgress {
prevTouchCount = 0
return
}
if prevTouchTimer != nil {
prevTouchTimer?.invalidate()
prevTouchTimer = nil
}
prevTouchCount += 1

for tapCount in tapHandlers.keys {
if tapCount > prevTouchCount { // wait some more - there is a recognizer for even more taps
prevTouchTimer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(onTouchTimer), userInfo: event, repeats: false)
return
}
}

for (tapCount, handlers) in tapHandlers {
if tapCount == prevTouchCount { // nothing to wait for - max tap count reached
handlers.forEach { handler in handler.handle(event) }
prevTouchCount = 0
}
}

}

@objc func onTouchTimer(timer: Timer) {
if isLongTapInProgress {
prevTouchCount = 0
return
}
for touchCount in tapHandlers.keys.sorted(by: {$0>$1}) {
if touchCount <= prevTouchCount, let event = timer.userInfo as? TapEvent {
// no more taps coming, settle for next best thing
for _ in 0..<prevTouchCount/touchCount { // might need to call it multiple times
tapHandlers[touchCount]?.forEach { handler in handler.handle(event) }
}
break
}
}
prevTouchCount = 0
}

// MARK: - Helpers

func handleLongTap( _ event: TapEvent, touchBegan: Bool ) {
isLongTapInProgress = touchBegan
if touchBegan {
return
}
longTapHandlers.forEach { handler in handler.handle(event) }
}

func handlePan( _ event: PanEvent ) {
Expand Down Expand Up @@ -206,6 +280,10 @@ open class Node: Drawable {
func shouldCheckForTap() -> Bool {
return !tapHandlers.isEmpty
}

func shouldCheckForLongTap() -> Bool {
return !longTapHandlers.isEmpty
}

func shouldCheckForPan() -> Bool {
return !panHandlers.isEmpty
Expand Down
1 change: 1 addition & 0 deletions Source/model/scene/SceneUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class SceneUtils {
shape.touchReleasedHandlers = from.touchReleasedHandlers

shape.tapHandlers = from.tapHandlers
shape.longTapHandlers = from.longTapHandlers
shape.panHandlers = from.panHandlers
shape.rotateHandlers = from.rotateHandlers
shape.pinchHandlers = from.pinchHandlers
Expand Down
41 changes: 41 additions & 0 deletions Source/views/MacawView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,26 @@ open class MacawView: MView, MGestureRecognizerDelegate {
self.animationCache = AnimationCache(sceneLayer: layer)

let tapRecognizer = MTapGestureRecognizer(target: self, action: #selector(MacawView.handleTap))
let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(MacawView.handleLongTap(recognizer:)))
let panRecognizer = MPanGestureRecognizer(target: self, action: #selector(MacawView.handlePan))
let rotationRecognizer = MRotationGestureRecognizer(target: self, action: #selector(MacawView.handleRotation))
let pinchRecognizer = MPinchGestureRecognizer(target: self, action: #selector(MacawView.handlePinch))

tapRecognizer.delegate = self
longTapRecognizer.delegate = self
panRecognizer.delegate = self
rotationRecognizer.delegate = self
pinchRecognizer.delegate = self

tapRecognizer.cancelsTouchesInView = false
longTapRecognizer.cancelsTouchesInView = false
panRecognizer.cancelsTouchesInView = false
rotationRecognizer.cancelsTouchesInView = false
pinchRecognizer.cancelsTouchesInView = false

self.removeGestureRecognizers()
self.addGestureRecognizer(tapRecognizer)
self.addGestureRecognizer(longTapRecognizer)
self.addGestureRecognizer(panRecognizer)
self.addGestureRecognizer(rotationRecognizer)
self.addGestureRecognizer(pinchRecognizer)
Expand Down Expand Up @@ -321,6 +325,43 @@ open class MacawView: MView, MGestureRecognizerDelegate {
}
}

// MARK: - Tap

@objc func handleLongTap(recognizer: UILongPressGestureRecognizer) {
if !self.node.shouldCheckForLongTap() {
return
}

guard let renderer = renderer else {
return
}

let location = recognizer.location(in: self)
var foundNodes = [Node]()

localContext { ctx in
guard let foundNode = renderer.findNodeAt(location: location, ctx: ctx) else {
return
}

var parent: Node? = foundNode
while parent != .none {
if parent!.shouldCheckForTap() {
foundNodes.append(parent!)
}

parent = nodesMap.parents(parent!).first
}
}

foundNodes.forEach { node in
let inverted = node.place.invert()!
let loc = location.applying(RenderUtils.mapTransform(inverted))
let event = TapEvent(node: node, location: Point(x: Double(loc.x), y: Double(loc.y)))
node.handleLongTap(event, touchBegan: recognizer.state == .began)
}
}

// MARK: - Pan

@objc func handlePan(recognizer: MPanGestureRecognizer) {
Expand Down