Question

Here is the UIPanGesture subclass:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"touches began");
   self ->origLoc = [[touches anyObject] locationInView:self.view.superview];
   [super touchesBegan:touches withEvent:event];
}


- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.state == UIGestureRecognizerStatePossible) {
    CGPoint loc = [[touches anyObject] locationInView:self.view.superview];
    CGFloat deltaX = fabsf(loc.x - origLoc.x);
    CGFloat deltaY = fabsf(loc.y - origLoc.y);
    if (deltaY >= deltaX) {
        self.state = UIGestureRecognizerStateFailed;
    } else {
    [super touchesMoved:touches withEvent:event];
    }
}
}

- (CGPoint) translationInView:(UIView *)v {

CGPoint proposedTranslation = [super translationInView:v];
proposedTranslation.y = 0;
return proposedTranslation;

}

Here is the method that gets called by my subclassed UIPanGesture:

- (void)dragging: (UIPanGestureRecognizer *)p {    

UIView *vv = p.view;

if (p.state == UIGestureRecognizerStateBegan || p.state == UIGestureRecognizerStateChanged) {
    CGPoint delta = [p translationInView:vv.superview];
    CGPoint c = vv.center;
    c.x += delta.x;
    c.y += delta.y;
    vv.center = c;
    [p setTranslation:CGPointZero inView:vv.superview];
 }    
}

When I try to drag my view around, it just moves a very small amount and then stops. I need to keep nudging it to get it to move. Any ideas?

Thanks in advance,

Michael.

Was it helpful?

Solution

In my prior comments, I wasn't clear why you were subclassing, but you clarified and it's because you have multiple gesture handlers going on. Very good.

So here is some sample code for a custom pan gesture, HorizontalPanGestureRecognizer, which is triggered only if the first movement is horizontal:

// HorizontalPanGestureRecognizer.h

#import <UIKit/UIKit.h>

@interface HorizontalPanGestureRecognizer : UIPanGestureRecognizer
@end

And

// HorizontalPanGestureRecognizer.m

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

@interface HorizontalPanGestureRecognizer ()
{
    BOOL _hasConfirmedDirection;
    BOOL _wasHorizontal;
    CGPoint _origLoc;
}
@end

@implementation HorizontalPanGestureRecognizer

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];

    UITouch *touch = [touches anyObject];

    _hasConfirmedDirection = NO;

    _origLoc = [touch locationInView:self.view];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    if (self.state == UIGestureRecognizerStateFailed) return;

    if (!_hasConfirmedDirection)
    {
        CGPoint translation = [self translationInView:self.view];

        _hasConfirmedDirection = YES;
        _wasHorizontal = (fabs(translation.x) > fabs(translation.y));
    }
    if (!_wasHorizontal)
        self.state = UIGestureRecognizerStateFailed;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];

    if (!_wasHorizontal)
        self.state = UIGestureRecognizerStateFailed;
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];

    if (!_wasHorizontal)
        self.state = UIGestureRecognizerStateFailed;
}

- (CGPoint)locationInView:(UIView *)view
{
    CGPoint superLocation = [super locationInView:view];

    if (_hasConfirmedDirection)
        superLocation.y = _origLoc.y;

    return superLocation;
}

- (CGPoint)translationInView:(UIView *)view
{
    CGPoint superTranslation = [super translationInView:view];

    if (_hasConfirmedDirection)
        superTranslation.y = 0.0f;

    return superTranslation;
}

@end

And then you can have your handler in your main view controller use it appropriately (in this example, just dragging a UILabel around). In in viewDidLoad, create the gesture:

HorizontalPanGestureRecognizer *recognizer = [[HorizontalPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleCustomPan:)];
[self.view addGestureRecognizer:recognizer];

And then the handler would look something like:

- (void)handleCustomPan:(UIPanGestureRecognizer *)sender 
{    
    switch (sender.state) {
        case UIGestureRecognizerStateChanged:
            if (!_panLabel)
            {
                // In my case I'm creating a UILabel to drag around, whereas you might just drag
                // whatever countrol you want to drag.
                //
                // But, regardless, I'm keeping track of the original center in _panLabelOrigCenter

                [self makePanLabel:sender]; 
            }

            CGPoint translate = [sender translationInView:self.view];
            _panLabel.center = CGPointMake(_panLabelOrigCenter.x + translate.x, _panLabelOrigCenter.y + translate.y);

            break;

        case UIGestureRecognizerStateEnded:
            [self removePanLabel];
            break;

        default:
            break;
    }
}

(Obviously, this is ARC code. If non-ARC, add the necessary additional lines of code for routine memory management.)

OTHER TIPS

That failure condition in touches moved looks like it will be super sensitive to early motion.

Consider making it less sensitive ...

- (BOOL)movementIsHorizontalX:(CGFloat)x andY:(CGFloat) y {

    CGFloat absX = fabs(x);
    CGFloat absY = fabs(y);

    // if x is small, then accept small y values but not large ones
    if (absX < 4) return absY < 4;

    // now we can express horizontal-ness as a slope.  we've ruled out small x, so this is a stable calculation
    CGFloat slope = absY / absX;
    return slope < 0.10;

    // you could generalize by passing in the x threshold and percentage slope constants
}

I realized my error. I moved

    [super touchesMoved: touches withEvent:event];  

outside the if statement, and now it seems to work fine. Not really 100% sure why it works now, but I am happy it does.

Now to figure out how to prevent the UIScrollView from accepting the pan gesture until this one cancels/fails...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top