Combining a UILongPressGestureRecognizer with a UIPanGestureRecognizer
S

7

20

I d'like to combine a UILongPressGestureRecognizer with a UIPanGestureRecognizer.

The UIPanGestureRecognizer should start with a long press. Is there a simple way to do this? or do I really have to write my own gesture recognizer?

I wan't something like on the home screen. You press on an icon and after some time the icons start wobbling. Afterwards without releasing my finger from the screen I can start dragging the icon under my finger around.

Shortcut answered 16/8, 2010 at 10:10 Comment(0)
S
16

I found a solution: This UIGestureRecognizerDelegate method does exactly what I looked for:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
Shortcut answered 16/8, 2010 at 12:51 Comment(0)
D
24

actually, you don't have to combine gesture recognizers - you can do this solely with UILongPressGestureRecognizer... You enter StateBegan once your touch(es) have stayed within 'allowableMovement' for 'minimumPressDuration'. You stay in your continuous longPressGesture as long as you don't lift any of your fingers - so you can start moving your fingers and track the movement through StateChanged.

Long-press gestures are continuous. The gesture begins (UIGestureRecognizerStateBegan) when the number of allowable fingers (numberOfTouchesRequired) have been pressed for the specified period (minimumPressDuration) and the touches do not move beyond the allowable range of movement (allowableMovement). The gesture recognizer transitions to the Change state whenever a finger moves, and it ends (UIGestureRecognizerStateEnded) when any of the fingers are lifted.

Deanery answered 26/8, 2010 at 22:52 Comment(2)
There a many issues that you can run into though. One of the best things about UIPanGestureRecognizer is that it lets you pan the whole window and still returns values, whereas UILongPressGestureRecognizer doesn't leave subviews (at least those with ClipsToBounds)Taiwan
Also, no velocity is available at when you're moving a LongPressCapias
G
21

I had a bit of a hard time for this problem. The accepted answer wasn't enough. No matter what I put in that method the pan or longpress handlers would get invoked. A solution I found was as follows:

  1. Ensure the gesture recognizers' delegates are assigned to the same class (in my case self) and ensure the delegate class is a UIGestureRecognizerDelegate.
  2. Add the following delegate method to your class (as per the answer above):

    - (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
    shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { 
         return YES;
    }
    
  3. Add the following delegate method to your class:

    - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
         if([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && ! shouldAllowPan) {
              return NO;
         }
         return YES;
    }
    
  4. Then add either a property or ivar which will track if the pan should be allowed to begin (see method above). In my case BOOL shouldAllowPan.

  5. Set the BOOL to NO in your init or viewDidLoad. Inside your longPress handler set the BOOL to YES. I do it like this:

    - (void) longPressHandler: (UILongPressGestureRecognizer *) gesture {
    
         if(UIGestureRecognizerStateBegan == gesture.state) {
            shouldAllowPan = NO;
         }
    
         if(UIGestureRecognizerStateChanged == gesture.state) {
            shouldAllowPan = YES;
         }
    }
    
  6. Inside the panHandler I do a check on the BOOL:

    - (void)panHandler:(UIPanGestureRecognizer *)sender{
        if(shouldAllowPan) {
              // do your stuff
        }
    
  7. And finally reset the BOOL within the panHandler:

    else if(sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateFailed || sender.state == UIGestureRecognizerStateCancelled) {
        shouldAllowPan = NO;
    }
    
  8. And then go grab a beer to congratulate yourself. ;)

Geilich answered 17/10, 2013 at 16:14 Comment(3)
+1. These steps really helped me to solve op's goal: to only allow drag after long press. Just 2 comments: in step 5 it is enough to check for Began and then set to YES. ``` if(UIGestureRecognizerStateBegan == gesture.state) { self.shouldAllowDrag = YES; } ``` And in step 6. it's not necessary to check for shouldAllowPan. -- sorry for the unformatted code.Aorta
I implemented Andy's solution and it worked beautifully. To get the responsiveness I wanted, I also changed the minimum long press duration: UILongPressGestureRecognizer *lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)]; lpgr.delegate = self; lpgr.minimumPressDuration = 0.05;Bloodroot
This helped me a lot. Thanks! I'll add in a Swift solution so that it can help others.Julieannjulien
S
16

I found a solution: This UIGestureRecognizerDelegate method does exactly what I looked for:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
Shortcut answered 16/8, 2010 at 12:51 Comment(0)
J
11

Andy B's approach in Swift,

  1. Add the UIGestureRecognizerDelegate delegate to the class

    class ViewController: UIViewController, UIGestureRecognizerDelegate
    
  2. Add a member variable

    var shouldAllowPan: Bool = false
    
  3. Add the gestures and need to add the pan gesture delegate to the VC. This is needed to fire off the shouldRecognizeSimultaneouslyWithGestureRecognizer and gestureRecognizerShouldBegin functions

    // long press
    let longPressRec = UILongPressGestureRecognizer(target: self, action: "longPress:")
    yourView.addGestureRecognizer(longPressRec)
    
    // drag
    let panRec = UIPanGestureRecognizer(target: self, action: "draggedView:")
    panRec.delegate = self
    yourView.addGestureRecognizer(panRec)
    
  4. Allow simultaneous gestures

    func gestureRecognizer(UIGestureRecognizer,
    shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
        // println("shouldRecognizeSimultaneouslyWithGestureRecognizer");
        return true
    }
    
    func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
         // We only allow the (drag) gesture to continue if it is within a long press
         if((gestureRecognizer is UIPanGestureRecognizer) && (shouldAllowPan == false)) {
             return false;
         }
         return true;
    }
    
  5. Inside the long press handler:

    func longPress(sender: UILongPressGestureRecognizer) {
    
        if(sender.state == .Began) {
            // handle the long press
        }
        else if(sender.state == .Changed){
            shouldAllowPan = true
    
        }
        else if (sender.state == .Ended) {
            shouldAllowPan = false
        }
    } 
    
Julieannjulien answered 4/5, 2015 at 14:42 Comment(4)
You forgot 6. Grab a beer. :) :)Geilich
@Julieannjulien Thanks for conversion and this worked like a charm.Festschrift
Awesome! Thanks! To enable both gestures (in my project, without the complication of using shouldAllowPan), I just needed to add UIGestureRecognizerDelegate, make the UIPanGestureRecognizer's delegate = self, and add the gestureRecognizer(_: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith:UIGestureRecognizer) function with return trueKimberykimble
@Kimberykimble same here 👍Formalin
T
1

For combinate more gesture :

  1. Create a local variable var shouldAllowSecondGesture : Bool = false
  2. Create the two recognizer

let longPressRec = UILongPressGestureRecognizer(target: self, action: #selector(self.startDrag(sender:))) cell.addGestureRecognizer(longPressRec) let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(sender:))) cell.isUserInteractionEnabled = true cell.addGestureRecognizer(panGestureRecognizer)

  1. Extension your VC and implement GestureRecognizerDelegate for implemented this method.

    extension YourViewController : UIGestureRecognizerDelegate {

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
            return true
        }
    
    
    
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
             // We only allow the (drag) gesture to continue if it is within a long press
             if((gestureRecognizer is UIPanGestureRecognizer) && (shouldAllowPan == false)) {
                 return false
             }
             return true
        }
    
    
    @objc func startDrag(sender:UIPanGestureRecognizer) {
    
        if(sender.state == .began) {
                // handle the long press
            }
        else if(sender.state == .changed){
                shouldAllowPan = true
    
            }
            else if (sender.state == .ended) {
                shouldAllowPan = false
            }
        }
    
Trudge answered 4/4, 2020 at 17:51 Comment(0)
M
0

Read the "Subclassing Notes" section of Apple's UIGestureRecognizer Class Reference at:

https://developer.apple.com/library/prerelease/tvos/documentation/UIKit/Reference/UIGestureRecognizer_Class/

Merrifield answered 12/1, 2016 at 5:38 Comment(0)
S
0

I solved this issue by implementing the desired functionality of the "action: Selector?" func of the UIPanGestureRecognizer within the "action: Selector?" func for the UILongPressGestureRecognizer.

As 'UILongPressGestureRecognizer' has no member 'translation', I calculated the translation by saving the position of the original touch and them extracting it from the actual touch position.


// in target class
var initialTouchX : CGFloat
var initialTouchX : CGFloat


// in the @objc func for the UILongPressGestureRecognizer
if sender.state == .began {
   initialTouchX = sender.location(in: sender.view).x
   initialTouchY = sender.location(in: sender.view).y
}

let translation = CGVector(dx: sender.location(in: sender.view).x - initialTouchX, dy: sender.location(in: sender.view).y - initialTouchY)


Sectarianism answered 12/1, 2021 at 13:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.