Skip to content

Commit

Permalink
Merge pull request #288 from f3dm76/task/tapEvents
Browse files Browse the repository at this point in the history
Fix #284: Support Multi-tap and Long Press Gestures
  • Loading branch information
ystrot authored Mar 27, 2018
2 parents 8ed98b1 + 0a179ca commit 2bd1ec8
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 7 deletions.
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

0 comments on commit 2bd1ec8

Please sign in to comment.