17

I'm moving UIView object using UIPanGestureRecognizer — how much I drag my finger on screen, that much I move the view in the same direction (only in X - left or right, Y is not changing). It works fine, but with (very noticeable) delay.

Here is the method that handles the UIPanGestureRecognizer event:

-(void)movePages:(UIPanGestureRecognizer *)sender
{
    if (switchingMode == 1) {
        if ([sender state] == UIGestureRecognizerStateBegan) {
            fingerStartPosition = [sender locationInView:self.view].x;
            viewStartPosition = [[viewControllers objectAtIndex:activeViewControllerIndex] view].center;
        }
        [[[[viewControllers objectAtIndex:activeViewControllerIndex] view] layer] setPosition:CGPointMake(viewStartPosition.x - (fingerStartPosition - [sender locationInView:self.view].x) , viewStartPosition.y)];            
    }
}

I've tried to set position of the view using its layer, I've also tried setting the frame, using animations with different durations, but everything behaved the same. Any idea why this delay occurs ?

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Michael
  • 723
  • 6
  • 18

3 Answers3

46

Use a UILongPressGestureRecognizer and set the minimumPressDuration to 0.0. This recognizes instantly and you get all the same updates including the UIGestureRecognizerStateChanged with the updated location.

Slipp D. Thompson
  • 33,165
  • 3
  • 43
  • 43
Derrick Hathaway
  • 1,087
  • 11
  • 14
  • 1
    Thought I would add - I was having an issue with a drawing field on a form when the form was zoomed (scaled) in - if you start drawing straight away it would scroll instead, you would have to hold your finger in place for a split second before drawing. Doing this at 100% scale it didn't happen, it would start drawing instantly. Replacing the UIPanGestureRecognizer with UILongPressGestureRecognizer worked a treat! – Jamie - Fenrir Digital Ltd Jan 22 '16 at 16:07
  • 6
    You'll also want to set the `UILongPressGestureRecognizer`'s `allowableMovement` to `CGFloat.infinity`.  Otherwise, the gesture will cancel itself after dragging a short distance (10pt). – Slipp D. Thompson Oct 06 '16 at 15:59
  • Strangely, to fix the exactly same issue than @EvilGeniusJamie, for me, it was the allowable movement mentionned by Slipp that I had to set. It was requiring a longer and longer distance once zoomed to 1.5. I had to drag at the speed of light to prevent the UIPan from cancelling and be able to trigger the Move event. Setting it to infinity did work. – Léon Pelletier Feb 08 '17 at 18:34
  • 3
    But with this solution, you won't have `translation(in:)` method, which is the reason why we need a `UIPanGestureRecognizer` :/ – Martin Jan 16 '19 at 09:01
  • 1
    It works, but there is no `velocity` when using long press recognizer. – LGP Sep 04 '19 at 11:43
  • unfortunately, sadly, **this is wrong in 2020** with recent iOS. it will detect the start of the gesture instantly BUT the first .moved report won't happen for some 8 or so pixels. (even if you try the allowableMovement trick.) Apple's gestures are useless for detailed, tiny movements. Just use touchesBegan ... – Fattie Mar 09 '20 at 19:57
4

I found that it was faster responding if you use just regular touchesBegan, Moved and Ended. I even subclassed a UIGestureRecognizer, and it still had lag on the panning gesture. Even though the touchesBegan within the UIGestureRecognizer would trigger on time, the state change would take a half second to change its state... It seems faster to just use a plain old TouchesBegan, especially if you're cpu is doing a lot.

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
    if touches.count == 1
    {
        initialTouchLocation = (touches.first?.locationInView(self).x)!
    }
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
{
    if touches.count == 1
    {
        let locationInView = touches.first?.locationInView(self)
        if !thresholdHit
        {
            //this is the threshold for x movement in order to trigger the panning...
            if abs(initialTouchLocation - locationInView!.x) > 1
            {
                thresholdHit = true
            }
        }
        else
        {
            if (self.frame.width != CGFloat(screenSize))
            {
                let panDelta = initialTouchLocation - locationInView!.x
            }
        }
    }
}
KorinW
  • 279
  • 4
  • 10
  • Agreed. In my case, I want to highlight the item I'm dragging around. For this to look good, the highlight needs to be drawn immediately. Seems as though the UIGestureRecognizer stuff is a tad slow at sending that initial action to the target which is just no use at all for my use case. – Chris Birch Apr 05 '17 at 22:47
  • 1
    note - just use the superview (location in ..) to prevent jitter, if you are actually moving the view – Fattie Mar 09 '20 at 19:58
3

The GestureRecognizer can't be sure, if it is a pan gesture, before you moved your finger some pixels. I don't know the exact tolerance value, but that is why you feel a delay.

Documentation:

A panning gesture is continuous. It begins when the minimum number of fingers allowed have moved enough to be considered a pan.

If you want instant movement, you probably need to build your own logic using touchesMoved:.

Another approach could be, to animate to the first recognized point. But that doesn't remove the delay. For that approach you could have a look at my JDDroppableView on github.

Slipp D. Thompson
  • 33,165
  • 3
  • 43
  • 43
calimarkus
  • 9,955
  • 2
  • 28
  • 48
  • it is not the delay at the beginning of the dragging I mean... when this delay is over, and object is already moving, it's moving with delay. I drag, object starts moving, but it's kind of slow and I have to wait until the object gets to the required position – Michael May 23 '12 at 22:11