Search code examples
iosuigesturerecognizernstimercabasicanimationcashapelayer

Draw a circle on a Long Press event


I am drawing a circle on the screen as the user taps on a button. The animation duration is already set and also the from and to value are also set.

What I want to achieve is that somehow the animation should commence as the user long presses the button and continues till he is maintaining the tap on the screen i.e for the duration of Long Press. As soon as the user lifts his finger the circle should stop to the point to where it has been completed till now.

Here is my code:

-(void)startCircularAnimation{

int radius = 50;
CAShapeLayer *circle = [CAShapeLayer layer];
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
                                         cornerRadius:radius].CGPath;
// Center the shape in self.view
circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
                              CGRectGetMidY(self.view.frame)-radius);

// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor redColor].CGColor;
circle.lineWidth = 5;

// Add to parent layer
[self.view.layer addSublayer:circle];

// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
drawAnimation.duration            = 15.0;
drawAnimation.repeatCount         = 1.0;  // Animate only once..

// Animate from no part of the stroke being drawn to the entire stroke being drawn
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue   = [NSNumber numberWithFloat:counter/drawAnimation.duration];

// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];

// Add the animation to the circle
[circle addAnimation:drawAnimation forKey:@"draw"];
}

This method performs animation and the from value is calculated from a timer which I started on the touch began case of the long press handler method. I am not able to get the perfect duration for the long press.

The long press event methods is something like this.

 - (void)_handleLongPressGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer{

    switch (gestureRecognizer.state) {
    case UIGestureRecognizerStateBegan:
    {
        counter = 0;
        timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(incrementCounter) userInfo:nil repeats:YES];


    }
    case UIGestureRecognizerStateEnded:{
        NSLog(@"State ended");
         [timer invalidate];

        break;
    }
    case UIGestureRecognizerStateCancelled:{
        NSLog(@"State cancelled");
        break;
    }
    case UIGestureRecognizerStateFailed:
    {

        break;
    }
    default:
        break;
  }

}

and the increment counter method is as follows

- (void)incrementCounter {
 counter++;
[self startCircularAnimation];
}

This is not giving me the desired effect for drawing the circle till the user has his finger on the screen.

Please suggest something in the code to get the desired functionality.

Thanks in advance.


Solution

  • You'll want to follow apples guidelines https://developer.apple.com/library/ios/qa/qa1673/_index.html

    So in your interface i'd declare the following

    @interface ViewController ()
    
    @property (nonatomic, strong) CAShapeLayer *circle;
    @property (nonatomic, strong) CABasicAnimation *drawAnimation;
    @property (strong, nonatomic) IBOutlet UIButton *circleButton;
    
    
    @end
    

    Then in view did load

    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        int radius = 50;
        self.circle = [CAShapeLayer layer];
        // Make a circular shape
        self.circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
                                              cornerRadius:radius].CGPath;
        // Center the shape in self.view
        self.circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
                                   CGRectGetMidY(self.view.frame)-radius);
    
        // Configure the apperence of the circle
        self.circle.fillColor = [UIColor clearColor].CGColor;
        self.circle.strokeColor = [UIColor redColor].CGColor;
        self.circle.lineWidth = 5;
    
        self.circle.strokeEnd = 0.0f;
    
        // Add to parent layer
        [self.view.layer addSublayer:_circle];
    
        // Target for touch down (hold down)
        [self.circleButton addTarget:self action:@selector(startCircleAnimation) forControlEvents:UIControlEventTouchDown];
    
        // Target for release
        [self.circleButton addTarget:self action:@selector(endCircleAnimation) forControlEvents:UIControlEventTouchUpInside];
    
        /**
         Don't start Animation in viewDidLoad to achive the desired effect
        */
    
    
    }
    

    Function to start the animation and resume it (probably needs a better name)

    -(void)startCircleAnimation{
        if (_drawAnimation) {
            [self resumeLayer:_circle];
        } else {
            [self circleAnimation];
        }
    }
    

    Function to end animation

    -(void)endCircleAnimation{
        [self pauseLayer:_circle];
    }
    

    Function to generate animation

    - (void)circleAnimation
    {
         // Configure animation
         self.drawAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
         self.drawAnimation.duration            = 10.0;
         self.drawAnimation.repeatCount         = 1.0; // Animate only once..
    
    
         // Animate from no part of the stroke being drawn to the entire stroke being drawn
         self.drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
    
         // Set your to value to one to complete animation
         self.drawAnimation.toValue   = [NSNumber numberWithFloat:1.0f];
    
         // Experiment with timing to get the appearence to look the way you want 
         self.drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    
         // Add the animation to the circle
         [self.circle addAnimation:_drawAnimation forKey:@"draw"];
    }
    

    Pause and stop functions from apple

       - (void)pauseLayer:(CALayer*)layer
    {
        CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
        layer.speed = 0.0;
        layer.timeOffset = pausedTime;
    }
    
      - (void)resumeLayer:(CALayer*)layer
      {
          CFTimeInterval pausedTime = [layer timeOffset];
          layer.speed = 1.0;
          layer.timeOffset = 0.0;
          layer.beginTime = 0.0;
          CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
          layer.beginTime = timeSincePause;
      }
    

    The main point you'd want to take from this is that your not keeping account of how long the button is pressed for your just keeping account of the events that are sent from the button UIControlEventTouchDown and UIControlEventTouchUpInside

    Edit:Gif

    Animation