2
votes

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.

2

2 Answers

3
votes

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


0
votes

I think you should make your from value related to value of counter, this will make the drawing start from what it was left behind last time the user lift his finger.

You should also make your timer's time interval smaller, 1 second is too long, 0.1 second will be better.