1

I'm trying to recreate an interaction similar to the photos app where you can pinch and pan a photo at the same time. Adding or removing a touch mid pan works perfectly.

In my code I'm using the location of touch to move the view. When I drag with two fingers, the pan gesture recognizers puts the point between the two fingers (as it should), but when I lift a finger it changes the point to that of that one finger, causing the view to jerk to a new position.

Setting the maximumNumberOfTouches to 1 does not solve my problem since you can touch with finger1, pan, touch with finger 2, pan, lift finger 1 and the view will jerk to the position of finger 2. Plus, I want to allow 2 finger panning since they can pinch to zoom and rotate the image as well.

I also cannot use UIScrollView for this for other reasons, but I know it doesn't have that problem.

The only solution I can think of is to get the initial touch location, then every time a finger is added or removed, offset the new location based on the old location. But I'm not sure how to get that information.

Is there an API for this? Is the above way the only way, and if so, how do I do it?

EdwardSanchez
  • 95
  • 1
  • 8
  • UIGestureRecognizer `location(in:)` is a centroid. It is _not_ the location of touch. Location of touch is `location(ofTouch:in:)` or UITouch `location(in:)`. – matt Nov 26 '17 at 07:52
  • I know. But that is basically what I want as a default. I posted a solution below. If you know of something better I'd love to know. – EdwardSanchez Nov 26 '17 at 22:57
  • The problem is that the correct way to respond to a pan gesture, to make a view draggable, is not by using `location(in:)`. As long as you do it that way, you're just making your life unnecessarily complicated. – matt Nov 27 '17 at 18:39

2 Answers2

4

As I understand it, the issue is that your code for responding to a pan (drag) doesn't work if the user changes the number of fingers in mid-drag, because the gesture recognizer's location(in:) jumps.

The problem is that the entire basic assumption underlying your code is wrong. To make a view draggable, you do not check the location(in:). You check the translation(in:). That's what it's for.

This is the standard pattern for making a view draggable with a pan gesture recognizer:

@objc func dragging(_ p : UIPanGestureRecognizer) {
    let v = p.view!
    switch p.state {
    case .began, .changed:
        let delta = p.translation(in:v.superview)
        var c = v.center
        c.x += delta.x; c.y += delta.y
        v.center = c
        p.setTranslation(.zero, in: v.superview)
    default: break
    }
}

That works fine even if the user starts with multiple fingers and lifts some during the drag.

matt
  • 515,959
  • 87
  • 875
  • 1,141
0

Ok, so here's how I solved it.

Inside the gesture function I have a global variable being given the touch location.

self.touchInView.x = sender.location(in: superview).x - frame.origin.x self.touchInView.y = sender.location(in: superview).y - frame.origin.y self.touchInParent = sender.location(in: superview)

In state == .began I have a variable called OriginalTouch which I set the location of touch.

if gesture.state == .began {
originalTouch = self.touchInView
}

Then in state == .changed I detect if the number of touches changed and calculate the offset:

//Reset original touch position if number of touch changes so view remains in the same position
if sender.numberOfTouches != lastNumberOfTouches {
   originalTouch.x += (touchInView.x - originalTouch.x)
   originalTouch.y += (touchInView.y - originalTouch.y)
}

lastNumberOfTouches = sender.numberOfTouches

Now I can set the view's location based on the originalTouch

self.frame.origin = touchInParent - originalTouch
EdwardSanchez
  • 95
  • 1
  • 8