UIPanGestureRecognizer do something immediately when touched
Asked Answered
A

7

23

I am new to ios, so I apologize in advance if I am missing something obvious.

I am creating a puzzle where I would like the individual puzzle pieces to increase in size on touch and decrease on letting go.

Currently I have:

-(IBAction)handlePan:(UIPanGestureRecognizer *)recognizer{
  if(recognizer.state == UIGestureRecognizerStateBegan)
  else if(recognizer.state == UIGestureRecognizerStateEnded)
}

The puzzle piece increases size when the pan begins (which is also when the statebegan) and decreases in size when the pan ends (as expected). I would like the size to increase once the user has touched the piece and before the puzzle piece moves. This is seen in Words With Friends when selecting a tile.

I have tried

-(IBAction)handleTap:(UITapGestureRecognizer *)recognizer{
  if(recognizer.state == UIGestureRecognizerStateBegan)
  else if(recognizer.state == UIGestureRecognizerStateEnded)
}

This will increase the puzzle piece only after the finger has lifted.

MY QUESTION:

Is there a way to increase the size of a puzzle piece once the finger has touched the puzzle piece and then continue with the pan gesture.

Thank you in advance.

Amando answered 12/7, 2013 at 18:12 Comment(1)
Yes, the "middle" state is missing. You have to track it to see if they've begun moving (translationInView).Puryear
K
42

I needed to do this too, and Jake's suggestion worked perfectly for me. In case it helps anyone who comes across this in the future, here is my subclass implementation of UIPanGestureRecognizer (the header remains unchanged):

#import "ImmediatePanGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>

@implementation ImmediatePanGestureRecognizer

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    self.state = UIGestureRecognizerStateBegan;
}

@end

This is all you need—this will fire as soon as you put your finger down on the view, update as soon as you move your finger a single point in any direction, and provide the functionality of a regular UIPanGestureRecognizer (like translationInView and velocityInView) before a regular one would've fired, all without breaking any existing functionality.


Copypaste for Swift 5, 2023

import UIKit // (nothing extra needed to import with Swift5)

fileprivate class ImmediatePanG: UIPanGestureRecognizer {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesBegan(touches, with: event)
        state = .began
    }
}
Kollwitz answered 2/10, 2013 at 19:36 Comment(4)
You are a genius, I would have never thought to do this. The gesture recognizer will call its selector twice with the same state value though...Titanesque
What if we are implementing a ImmediatePanGestureRecognizer on a UIview which itself has a touchesBegan method? how can we prioritize it?Skillet
@GeorgeWS, if you see this message I simply fixed the current Swift in your "famous answer" here! Obviously, feel free rollback, edit etc, cheers!Indigestive
@NateSymer, you can add if state == .began { return } However. IMO. I wouldn't do that. We're adding a "new state" if you will. (Think of it as ".earlyBegan" or ".downBegan" or ".specialStackoverflowBegan" !!!) It's different in nature from the "usual .began that Apple decides on" and it could (for example) happen at a different x-position etc. I guess a fuller solution would be to add a new state (". downBegan") and then upstream you could process as you wish. Thus, I would put it this way: Add if state == .began { return } to have it "not report the "usual" began", if desired.Indigestive
P
9

Swift 3/4/5 Solution

class InstantPanGestureRecognizer: UIPanGestureRecognizer {

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        if self.state == .began { return }
        super.touchesBegan(touches, with: event)
        self.state = .began
    }

}
Priscillaprise answered 27/8, 2017 at 17:25 Comment(1)
(You don't need the selfs in this code) Note for anyone googling here. Take care about adding the "if state == .began { return }" code. It would mean you're replacing, rather than adding to, the states. See discussion under the ticked answer.Indigestive
S
8

I have implemented George WS's answer. With a little testing I realized that additional touch events that occur after the initial touch, but before the initial touch ends are not being properly handled. Here is my updated implementation. It's a bit naive, but prevents bizarre behavior caused by the UIGestureRecognizerStateBegan happening multiple times.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if (self.state >= UIGestureRecognizerStateBegan) {
        return;
    }

    [super touchesBegan:touches withEvent:event];
    self.state = UIGestureRecognizerStateBegan;
}
Sabrinasabsay answered 8/4, 2014 at 12:41 Comment(0)
P
2

According to the documentation UIPanGestureRecognizer only enters UIGestureRecognizerStateBegan when "the minimum number of fingers allowed (minimumNumberOfTouches) has moved enough to be considered a pan". If you want something to happen as soon as you touch, you could try subclassing UIPanGestureRecognizer and over riding touchesBegan:withEvent:.

Priesthood answered 12/7, 2013 at 20:10 Comment(0)
B
2

Thanks George WS & Derrick Hathaway

Swift 2.2

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent) {
  if (self.state == UIGestureRecognizerState.Began) {
    return
  }
  super.touchesBegan(touches, withEvent: event)
  self.state = UIGestureRecognizerState.Began;
}

And you must add to your Bridging-Header:

#import <UIKit/UIGestureRecognizerSubclass.h>
Boothman answered 24/8, 2016 at 21:26 Comment(0)
I
2

I draw in a view that is part of a UIScrollView. I use a UIPanGestureRecognizer to draw in the view. This UIPanGestureRecognizer has minimumNumberOfTouches and maximumNumberOfTouches set to 1.

I use the UIPinchGestureRecognizer and the UIPanGestureRecognizer from the scrollView for panning and zooming. The UIPanGestureRecognizer of the scrollView has minimumNumberOfTouches and maximumNumberOfTouches set to 2.

I used the solution of George WS and I noticed that the drawing started quick, but it was difficult to recognize the pinch gesture for zooming and the two-finger pan gesture for panning. I changed the solution slightly and I use touchesMoved to recognize the start of the drawing. Zooming and panning was better recognized.

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
    super.touchesMoved(touches, with: event)
    state = UIGestureRecognizer.State.began
}
Interrelated answered 23/12, 2018 at 16:6 Comment(1)
That was my case too. Thanks for the idea!En
W
0

I'm trying to do a similar thing, and the resolution I've been working on is to use the view's touchesBegan:withEvent: to perform the actions I want to happen the instant the user touches the screen. Then the gesture handler takes over once the touch becomes a gesture.

Whichever answered 31/7, 2013 at 19:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.