I need to use a UILongTapGestureRecognizer
with a UIPinchGestureRecognizer
simultaneously.
Unfortunately, the first touch of the UILongTapGestureRecognizer
will be counted for the PinchGestureRecognizer
too. So when holding UILongTapGestureRecognizer
there just needs to be one more touch to trigger the Pinch Recognizer. One is used for long press gesture and two for the pinch.
Is there a way to use both independently? I do not want to use the touch of the UILongTapGestureRecognizer
for my UIPinchGestureRecognizer
.
This is how I enable my simultaneous workforce:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
//Allowing
if (gestureRecognizer == zoom) && (otherGestureRecognizer == longTap) {
print("working while filming")
return true
}
return false
}
I don't believe you have the tool for what you are looking for so I suggest you try to create your own gesture recognizer. It is not really that hard but you will unfortunately need to do both the long press and the pinch effect.
Don't try overriding UIPinchGestureRecognizer
nor UILongPressGestureRecognizer
because it will simply not work (or if you mange it please do share your findings). So just go straight for UIGestureRecognizer
.
So to begin with long press gesture recognizer we need to track that user presses and holds down for long enough time without moving too much. So we have:
var minimumPressDuration = UILongPressGestureRecognizer().minimumPressDuration
var allowableMovement = UILongPressGestureRecognizer().allowableMovement
Now touches need to be overridden (this is all in subclass of gesture recognizer):
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
touchMoveDistance = 0.0 // Reset the movement to zero
previousLocation = touches.first?.location(in: view) // We need to save the previous location
longPressTimer = Timer.scheduledTimer(timeInterval: minimumPressDuration, target: self, selector: #selector(onTimer), userInfo: nil, repeats: false) // Initiate a none-repeating timer
if inProgress == false { // inProgress will return true when stati is either .began or .changed
super.touchesBegan(touches, with: event)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
if let newLocation = touches.first?.location(in: view), let previousLocation = previousLocation {
self.previousLocation = newLocation
touchMoveDistance += abs(previousLocation.y-newLocation.y) + abs(previousLocation.x-newLocation.x) // Don't worry about precision of this. We can't know the path between the 2 points anyway
}
if inProgress == false {
super.touchesMoved(touches, with: event)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
longPressTimer?.invalidate()
longPressTimer = nil
if inProgress {
state = .ended
}
super.touchesEnded(touches, with: event)
if self.isEnabled { // This will simply reset the gesture
self.isEnabled = false
self.isEnabled = true
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
longPressTimer?.invalidate()
longPressTimer = nil
if inProgress {
state = .ended
}
super.touchesCancelled(touches, with: event)
if self.isEnabled {
self.isEnabled = false
self.isEnabled = true
}
}
So these are all only for long press. And what happens on timer is:
@objc private func onTimer() {
longPressTimer?.invalidate()
longPressTimer = nil
if state == .possible {
state = .began
}
}
So basically if we change state to .begin
we trigger the gesture and rest of the events simply work. Which is pretty neat.
Unfortunately this is far from over and you will need to play around a bit with the rest of the code...
You will need to preserve touches (If I remember correctly the same touch will be reported as a comparable object until user lifts his finger):
longPressTouch
didSucceedLongPress = true
if longPressTouch != nil && didSucceedLongPress == false { cancel() }
. Or allow it, this really depends on what you want.pinchTouches.append((touch: touch, initialPosition: touchPosition))
So this should be all the data you need for your pinch gesture recognizer. Since all the events should already be triggering for you the way you need it all you need is a computed value for your scale:
var pinchScale: CGFloat {
guard didSucceedLongPress, pinchTouches.count >= 2 else {
return 1.0 // Not having enough data yet
}
return distanceBetween(pinchTouches[0].touch, pinchTouches[1].touch)/distanceBetween(pinchTouches[0].initialPosition, pinchTouches[1].initialPosition) // Shouldn't be too hard to make
}
Then there are some edge cases you need to check like: user initiates a long press, uses 2 fingers to pinch, adds 3rd finger (ignored), removes 2nd finger: Without handling this you might get a little jump which may or may not be intended. I guess you could just cancel the gesture or you could somehow transform the initial values to make the jump disappear.
So good luck if you will be implementing this and let us know how it went.