2

I am trying to allow a user to grab a UIView that is being animated at the completion of a UIPanGestureRecognizer method. I am currently able to stop the view and center it under the finger using touchesBegan and the view's presentation layer. But I want the user to be able to retrigger the UIPanGestureRecognizer method without having to lift the finger up and replace it on the UIView.

I've been working on this challenge for a week or so, and have run through various search strings on SO and elsewhere, read through Ray Wenderlich's tutorials, etc., but still haven't found a solution. It looks like I'm trying to do something similar to this question that seemed unresolved:

iOS - UIPanGestureRecognizer : drag during animation

I have just been working with UIView animation blocks and have yet to dive into CABasicAnimation work. Is it possible to do what I want by changing the code below somehow?

Thank you in advance.

In FlingViewController.h:

#import <UIKit/UIKit.h>

@interface FlingViewController : UIViewController <UIGestureRecognizerDelegate>
{
    UIView *myView;
}

@property (nonatomic, weak) UIView *myView;



@end

In FlingViewController.m:

#import "FlingViewController.h"
#import <QuartzCore/QuartzCore.h>

@interface FlingViewController ()

@end

@implementation FlingViewController

@synthesize myView = _myView;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // Allocate and init view
    myView = [[UIView alloc] initWithFrame:CGRectMake(self.view.center.x - 50,
                                                      self.view.center.y - 50,
                                                      100, 100)];

    // Set view background color
    [myView setBackgroundColor:[UIColor purpleColor]];

    // Create pan gesture recognizer
    UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc]
                                       initWithTarget:self action:@selector(handleGesture:)];

    // Add gesture recognizer to view
    gesture.delegate = self;
    [myView addGestureRecognizer:gesture];

    // Add myView as subview
    [self.view addSubview:myView];
    NSLog(@"myView added");


}

- (void)handleGesture:(UIPanGestureRecognizer *)recognizer
{
    if ((recognizer.state == UIGestureRecognizerStateBegan) ||
        (recognizer.state == UIGestureRecognizerStateChanged))
    {
        [recognizer.view.layer removeAllAnimations];

        CGPoint translation = [recognizer translationInView:self.view];
        recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
                                             recognizer.view.center.y + translation.y);
        [recognizer setTranslation:CGPointMake(0, 0) inView:self.view];

    }
    else if (recognizer.state == UIGestureRecognizerStateEnded)
    {
        CGPoint velocity = [recognizer velocityInView:self.view];
        CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
        CGFloat slideMult = magnitude / 200;

        float slideFactor = 0.1 * slideMult;
        CGPoint finalPoint = CGPointMake(recognizer.view.center.x + (velocity.x * slideFactor),
                                         recognizer.view.center.y + (velocity.y * slideFactor));
        finalPoint.x = MIN(MAX(finalPoint.x, 0), self.view.bounds.size.width);
        finalPoint.y = MIN(MAX(finalPoint.y, 0), self.view.bounds.size.height);

        [UIView animateWithDuration:slideFactor * 2
                              delay:0
                            options:(UIViewAnimationOptionCurveEaseOut|
                                     UIViewAnimationOptionAllowUserInteraction |
                                     UIViewAnimationOptionBeginFromCurrentState)
                         animations:^{
                             recognizer.view.center = finalPoint;
                         }
                         completion:^(BOOL finished){
                             NSLog(@"Animation complete");
                         }];
    }
}


// Currently, this method will detect when user touches
// myView's presentationLayer and will center that view under the finger
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInView:self.view];

    if ([[myView.layer presentationLayer] hitTest:touchPoint])
    {
        NSLog(@"Touched!");

        [myView.layer removeAllAnimations];

        myView.center = touchPoint;

        // *** NOTE ***
        // At this point, I want the user to be able to fling
        // the view around again without having to lift the finger
        // and replace  it on the view;  i.e. when myView is moving
        // and the user taps it, it should stop moving and follow
        // user's pan gesture again
        // Similar to unresolved SO question:
        // https://stackoverflow.com/questions/13234234/ios-uipangesturerecognizer-drag-during-animation

    }
}


- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
Community
  • 1
  • 1
sine
  • 31
  • 2

1 Answers1

1

In case it helps anyone else, I've found a solution that seems to work pretty well, based on Thomas Rued/Jack's response to this SO question:

CAAnimation - User Input Disabled

I am using two UIPanGestureRecognizers to move the UIView -- one (gesture/handleGesture:) is attached to the UIView (myView), and another (bigGesture/superGesture:) is attached to that UIView's superview (bigView). The superview detects the position of the subview's presentation layer as it is animating.

The only issue (so far) is the user can still grab the animated view by touching the ending location of myView's animation before the animation completes. Ideally, only the presentation layer location would be the place where the user can interact with the view. If anyone has insight into preventing premature interaction, it would be appreciated.

#import "FlingViewController.h"
#import <QuartzCore/QuartzCore.h>

@interface FlingViewController ()

@end

@implementation FlingViewController

@synthesize myView = _myView;
@synthesize bigView = _bigView;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // *** USING A GESTURE RECOGNIZER ATTACHED TO THE SUPERVIEW APPROACH
    // CAME FROM SO USER THOMAS RUED/JACK AT FOLLOWING LINK:
    // https://stackoverflow.com/questions/7221688/caanimation-user-input-disabled

    // Set up size for superView (bigView)
    CGRect screenRect = [[UIScreen mainScreen] bounds];

    // Allocate big view
    bigView = [[UIView alloc] initWithFrame:(screenRect)];

    [bigView setBackgroundColor:[UIColor greenColor]];

    [self.view addSubview:bigView];

    // Allocate and init myView
    myView = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 100, 100)];

    // Set view background color
    [myView setBackgroundColor:[UIColor purpleColor]];

    // Create pan gesture recognizer
    UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc]
                                       initWithTarget:self action:@selector(handleGesture:)];

    UIPanGestureRecognizer *bigGesture = [[UIPanGestureRecognizer alloc]
                                            initWithTarget:self action:@selector(superGesture:)];

    // Add gesture recognizer to view
    gesture.delegate = self;
    bigGesture.delegate = self;
    [myView addGestureRecognizer:gesture];
    [bigView addGestureRecognizer:bigGesture];

    // Add myView as subview of bigView
    [bigView addSubview:myView];
    NSLog(@"myView added");

}

// This gesture recognizer is attached to the superView,
// and will detect myView's presentation layer while animated
- (void)superGesture:(UIPanGestureRecognizer *)recognizer
{    
    CGPoint location = [recognizer locationInView:self.view];

    for (UIView* childView in recognizer.view.subviews)
    {
        CGRect frame = [[childView.layer presentationLayer] frame];

        if (CGRectContainsPoint(frame, location))
        // ALTERNATE 'IF' STATEMENT; BOTH SEEM TO WORK:
        //if ([[childView.layer presentationLayer] hitTest:location])
        {
            NSLog(@"location = %.2f, %.2f", location.x, location.y);
            [childView.layer removeAllAnimations];
            childView.center = location;
        }

        if ((recognizer.state == UIGestureRecognizerStateEnded) &&
            (CGRectContainsPoint(frame, location)))
        {
            CGPoint velocity = [recognizer velocityInView:self.view];
            CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
            CGFloat slideMult = magnitude / 200;

            float slideFactor = 0.1 * slideMult;
            CGPoint finalPoint = CGPointMake(childView.center.x + (velocity.x * slideFactor),
                                             childView.center.y + (velocity.y * slideFactor));
            finalPoint.x = MIN(MAX(finalPoint.x, 0), self.view.bounds.size.width);
            finalPoint.y = MIN(MAX(finalPoint.y, 0), self.view.bounds.size.height);

            [UIView animateWithDuration:slideFactor * 2
                                  delay:0
                                options:(UIViewAnimationOptionCurveEaseOut|
                                         UIViewAnimationOptionAllowUserInteraction |
                                         UIViewAnimationOptionBeginFromCurrentState)
                             animations:^{
                                 childView.center = finalPoint;
                             }
                             completion:^(BOOL finished){
                                 NSLog(@"Big animation complete");
                             }];
        }
    }
}

// This gesture recognizer is attached to myView,
// and will handle movement when myView is not animating
- (void)handleGesture:(UIPanGestureRecognizer *)recognizer
{

    if ((recognizer.state == UIGestureRecognizerStateBegan) ||
        (recognizer.state == UIGestureRecognizerStateChanged))
    {
        [recognizer.view.layer removeAllAnimations];

        CGPoint translation = [recognizer translationInView:self.view];
        recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
                                             recognizer.view.center.y + translation.y);
        [recognizer setTranslation:CGPointMake(0, 0) inView:self.view];

    }
    else if (recognizer.state == UIGestureRecognizerStateEnded)
    {
        CGPoint velocity = [recognizer velocityInView:self.view];
        CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
        CGFloat slideMult = magnitude / 200;

        float slideFactor = 0.1 * slideMult;
        CGPoint finalPoint = CGPointMake(recognizer.view.center.x + (velocity.x * slideFactor),
                                         recognizer.view.center.y + (velocity.y * slideFactor));
        finalPoint.x = MIN(MAX(finalPoint.x, 0), self.view.bounds.size.width);
        finalPoint.y = MIN(MAX(finalPoint.y, 0), self.view.bounds.size.height);

        [UIView animateWithDuration:slideFactor * 2
                              delay:0
                            options:(UIViewAnimationOptionCurveEaseOut|
                                     UIViewAnimationOptionAllowUserInteraction |
                                     UIViewAnimationOptionBeginFromCurrentState)
                         animations:^{
                             recognizer.view.center = finalPoint;
                         }
                         completion:^(BOOL finished){
                             NSLog(@"Animation complete");
                         }];
    }

}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
Community
  • 1
  • 1
sine
  • 31
  • 2