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):
- On begin save the touch to a private property
longPressTouch
- On timer when long press succeeds set a property to indicate the long press has indeed triggered
didSucceedLongPress = true
- On begin check if another touch can be added and cancel gesture if it may not
if longPressTouch != nil && didSucceedLongPress == false { cancel() }
. Or allow it, this really depends on what you want.
- On begin if touches may be added then add them in array and save their initial positions
pinchTouches.append((touch: touch, initialPosition: touchPosition))
- On touches end and cancel make sure to remove appropriate touches from array. And if long press is removed cancel the event (or not, your choice again)
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.