raman
raman

Reputation: 175

How to display custom circular progress bar with Anti-Clockwise animation?

I'm working with Quiz related App. Here will display the custom circular progress bar based the percentage value from correct answers divider by total questions and multiply by 100. And get the resulted value as percentage and then the resulted value divided by 100 for get the float values, because the progress animation value is "0.0 to 1.0" Here I use the library "DACircularProgressView". Now the progress working with clockwise animation. But I need anti clockwise animation.

If you anybody know kindly give the siggestion. I really don't know how to change rotation animation in "DACircularProgressView".

    //
//  DACircularProgressView.h
//  DACircularProgress
//
//  Created by Daniel Amitay on 2/6/12.
//  Copyright (c) 2012 Daniel Amitay. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface DACircularProgressView : UIView

@property(nonatomic, strong) UIColor *trackTintColor UI_APPEARANCE_SELECTOR;
@property(nonatomic, strong) UIColor *progressTintColor UI_APPEARANCE_SELECTOR;
@property(nonatomic) NSInteger roundedCorners UI_APPEARANCE_SELECTOR; // Can not use BOOL with UI_APPEARANCE_SELECTOR :-(
@property(nonatomic) CGFloat thicknessRatio UI_APPEARANCE_SELECTOR;
@property(nonatomic) CGFloat progress;

@property(nonatomic) CGFloat indeterminateDuration UI_APPEARANCE_SELECTOR;
@property(nonatomic) NSInteger indeterminate UI_APPEARANCE_SELECTOR; // Can not use BOOL with UI_APPEARANCE_SELECTOR :-(

- (void)setProgress:(CGFloat)progress animated:(BOOL)animated;

@end


//
//  DACircularProgressView.m
//  DACircularProgress
//
//  Created by Daniel Amitay on 2/6/12.
//  Copyright (c) 2012 Daniel Amitay. All rights reserved.
//

#import "DACircularProgressView.h"

#import <QuartzCore/QuartzCore.h>

@interface DACircularProgressLayer : CALayer

@property(nonatomic, strong) UIColor *trackTintColor;
@property(nonatomic, strong) UIColor *progressTintColor;
@property(nonatomic) NSInteger roundedCorners;
@property(nonatomic) CGFloat thicknessRatio;
@property(nonatomic) CGFloat progress;

@end

@implementation DACircularProgressLayer

@dynamic trackTintColor;
@dynamic progressTintColor;
@dynamic roundedCorners;
@dynamic thicknessRatio;
@dynamic progress;

+ (BOOL)needsDisplayForKey:(NSString *)key
{
    return [key isEqualToString:@"progress"] ? YES : [super needsDisplayForKey:key];
}

- (void)drawInContext:(CGContextRef)context
{
    CGRect rect = self.bounds;
    CGPoint centerPoint = CGPointMake(rect.size.height / 2, rect.size.width / 2);
    CGFloat radius = MIN(rect.size.height, rect.size.width) / 2;

    CGFloat progress = MIN(self.progress, 1.f - FLT_EPSILON);
    CGFloat radians = (progress * 2 * M_PI) - M_PI_2;

    CGContextSetFillColorWithColor(context, self.trackTintColor.CGColor);
    CGMutablePathRef trackPath = CGPathCreateMutable();
    CGPathMoveToPoint(trackPath, NULL, centerPoint.x, centerPoint.y);
    CGPathAddArc(trackPath, NULL, centerPoint.x, centerPoint.y, radius, 3 * M_PI_2, -M_PI_2, NO);
    CGPathCloseSubpath(trackPath);
    CGContextAddPath(context, trackPath);
    CGContextFillPath(context);
    CGPathRelease(trackPath);

    if (progress > 0.f)
    {
        CGContextSetFillColorWithColor(context, self.progressTintColor.CGColor);
        CGMutablePathRef progressPath = CGPathCreateMutable();
        CGPathMoveToPoint(progressPath, NULL, centerPoint.x, centerPoint.y);
        CGPathAddArc(progressPath, NULL, centerPoint.x, centerPoint.y, radius, 3 * M_PI_2, radians, NO);
        CGPathCloseSubpath(progressPath);
        CGContextAddPath(context, progressPath);
        CGContextFillPath(context);
        CGPathRelease(progressPath);
    }

    if (progress > 0.f && self.roundedCorners)
    {
        CGFloat pathWidth = radius * self.thicknessRatio;
        CGFloat xOffset = radius * (1.f + ((1 - (self.thicknessRatio / 2.f)) * cosf(radians)));
        CGFloat yOffset = radius * (1.f + ((1 - (self.thicknessRatio / 2.f)) * sinf(radians)));
        CGPoint endPoint = CGPointMake(xOffset, yOffset);

        CGContextAddEllipseInRect(context, CGRectMake(centerPoint.x - pathWidth / 2, 0, pathWidth, pathWidth));
        CGContextFillPath(context);

        CGContextAddEllipseInRect(context, CGRectMake(endPoint.x - pathWidth / 2, endPoint.y - pathWidth / 2, pathWidth, pathWidth));
        CGContextFillPath(context);
    }

    CGContextSetBlendMode(context, kCGBlendModeClear);
    CGFloat innerRadius = radius * (1.f - self.thicknessRatio);
    CGPoint newCenterPoint = CGPointMake(centerPoint.x - innerRadius, centerPoint.y - innerRadius);
    CGContextAddEllipseInRect(context, CGRectMake(newCenterPoint.x, newCenterPoint.y, innerRadius * 2, innerRadius * 2));
    CGContextFillPath(context);
}

@end

@implementation DACircularProgressView

+ (void) initialize
{
    if (self != [DACircularProgressView class])
        return;

    id appearance = [self appearance];
    [appearance setTrackTintColor:[[UIColor whiteColor] colorWithAlphaComponent:0.3f]];
    [appearance setProgressTintColor:[UIColor whiteColor]];
    [appearance setThicknessRatio:0.3f];
    [appearance setRoundedCorners:NO];

    [appearance setIndeterminateDuration:2.0f];
    [appearance setIndeterminate:NO];
}

+ (Class)layerClass
{
    return [DACircularProgressLayer class];
}

- (DACircularProgressLayer *)circularProgressLayer
{
    return (DACircularProgressLayer *)self.layer;
}

- (id)init
{
    return [self initWithFrame:CGRectMake(0.0f, 0.0f, 40.0f, 40.0f)];
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        self.backgroundColor = [UIColor clearColor];
    }
    return self;
}

- (void)didMoveToWindow
{
    self.circularProgressLayer.contentsScale = [UIScreen mainScreen].scale;
}

#pragma mark - Progress

-(CGFloat)progress
{
    return self.circularProgressLayer.progress;
}

- (void)setProgress:(CGFloat)progress
{
    [self setProgress:progress animated:NO];
}

- (void)setProgress:(CGFloat)progress animated:(BOOL)animated
{
    CGFloat pinnedProgress = MIN(MAX(progress, 0.f), 1.f);
    if (animated)
    {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"progress"];
        animation.duration = fabsf(self.progress - pinnedProgress); // Same duration as UIProgressView animation
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        animation.fromValue = [NSNumber numberWithFloat:self.progress];
        animation.toValue = [NSNumber numberWithFloat:pinnedProgress];
        [self.circularProgressLayer addAnimation:animation forKey:@"progress"];
//        [self.circularProgressLayer setFrame:CGRectMake(3, 3, 40, 40)];
    }
    else
    {
        [self.circularProgressLayer setNeedsDisplay];
    }
    self.circularProgressLayer.progress = pinnedProgress;
}

#pragma mark - UIAppearance methods

- (UIColor *)trackTintColor
{
    return self.circularProgressLayer.trackTintColor;
}

- (void)setTrackTintColor:(UIColor *)trackTintColor
{
    self.circularProgressLayer.trackTintColor = trackTintColor;
    [self.circularProgressLayer setNeedsDisplay];
}

- (UIColor *)progressTintColor
{
    return self.circularProgressLayer.progressTintColor;
}

- (void)setProgressTintColor:(UIColor *)progressTintColor
{
    self.circularProgressLayer.progressTintColor = progressTintColor;
    [self.circularProgressLayer setNeedsDisplay];
}

- (NSInteger)roundedCorners
{
    return self.roundedCorners;
}

-(void)setRoundedCorners:(NSInteger)roundedCorners
{
    self.circularProgressLayer.roundedCorners = roundedCorners;
    [self.circularProgressLayer setNeedsDisplay];
}

-(CGFloat)thicknessRatio
{
    return self.circularProgressLayer.thicknessRatio;
}

- (void)setThicknessRatio:(CGFloat)thicknessRatio
{
    self.circularProgressLayer.thicknessRatio = MIN(MAX(thicknessRatio, 0.f), 1.f);
    [self.circularProgressLayer setNeedsDisplay];
}

- (NSInteger)indeterminate
{
    CAAnimation *spinAnimation = [self.layer animationForKey:@"indeterminateAnimation"];
    return spinAnimation;
}

- (void)setIndeterminate:(NSInteger)indeterminate
{
    if (indeterminate && !self.indeterminate)
    {
        CABasicAnimation *spinAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
        spinAnimation.byValue = [NSNumber numberWithFloat:2.0f*M_PI];
        spinAnimation.duration = self.indeterminateDuration;
        spinAnimation.repeatCount = HUGE_VALF;
        [self.layer addAnimation:spinAnimation forKey:@"indeterminateAnimation"];
    }
    else
    {
        [self.layer removeAnimationForKey:@"indeterminateAnimation"];
    }
}

@end

In my own class,

    self.largeProgressView = [[DACircularProgressView alloc] initWithFrame:CGRectMake(10.0f, 85.0f, 78.0f, 78.0f)];
self.largeProgressView.roundedCorners    = NO;
        self.largeProgressView.trackTintColor    = THIK_GRAY_COLOR;
        self.largeProgressView.progressTintColor = LIGHT_GREEN_COLOR;
        self.largeProgressView.thicknessRatio    = 0.2f;
        [self.largeProgressView setBackgroundColor:[UIColor clearColor]];
        [resultatsCategoryView addSubview:self.largeProgressView];

total   = [TotalQuestionsCount floatValue];
        current = [CorrectAnswersCount floatValue];
        percentageCompleted = current / total * 100;
        percentageCompleted = percentageCompleted / 100;
        //NSLog(@"percentageCompleted = %f",percentageCompleted);

        for (DACircularProgressView *progressView in [NSArray arrayWithObjects:self.largeProgressView, nil])
        {
            CGFloat progress = percentageCompleted;
            //NSLog(@"progress = %f",progress);
            [progressView setProgress:progress animated:YES];

            if (progressView.progress >= 1.0f && [self.timer isValid])
            {
                [progressView setProgress:0.f animated:YES];
            }
        }

Upvotes: 0

Views: 3169

Answers (2)

Rubick
Rubick

Reputation: 29

I don't know if someone still have problems with this, but here is solution that moves animation from Empty to Full in both directions.

Code:

class CirclingVC: UIViewController {
    
    let trackLayer = CAShapeLayer()
    let shapeLayer = CAShapeLayer()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Create Path on which Shape will fill
        let trackPath = UIBezierPath(arcCenter: view.center,
                                        radius: 100,
                                        startAngle: -.pi/2,
                                        endAngle: 2 * .pi,
                                        clockwise: true)
        
        trackLayer.path = trackPath.cgPath
        trackLayer.strokeColor = UIColor.lightGray.cgColor
        trackLayer.lineWidth = 10
        trackLayer.fillColor = UIColor.clear.cgColor
        
        view.layer.addSublayer(trackLayer)
        
    }
    
    @IBAction func leftCirclingPressed(_ sender: UIButton) {
        animateCircling(clockWise: false)
    }
    
    
    @IBAction func rightCirclingPressed(_ sender: UIButton) {
        animateCircling(clockWise: true)
    }
    
    private func animateCircling(clockWise: Bool) {
        // Create Shape that fills the circle
        let shapePath = UIBezierPath(arcCenter: view.center,
                                 radius: 100,
                                 startAngle: clockWise ? (-.pi/2) : (3.5 * .pi),
                                 endAngle: clockWise ? (2 * .pi) : (.pi),
                                 clockwise: clockWise)
        
        shapeLayer.path = shapePath.cgPath
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.lineWidth = 10
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineCap = CAShapeLayerLineCap.round
        shapeLayer.strokeEnd = 0
        view.layer.addSublayer(shapeLayer)
        
        // Animation
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.toValue = 1
        animation.duration = 2
        animation.timingFunction = CAMediaTimingFunction(name: .linear)
        animation.fillMode = .forwards
        animation.isRemovedOnCompletion = true
        
        shapeLayer.add(animation, forKey: "circling")
    }
  }

Results:

GIF

Upvotes: 0

Shankar BS
Shankar BS

Reputation: 8402

use below code i hav changed a bit replace above .m file by below .m file hope this helps u


 #import "DACircularProgressView.h"

 #import <QuartzCore/QuartzCore.h>

 @interface DACircularProgressLayer : CALayer

 @property(nonatomic, strong) UIColor *trackTintColor;
 @property(nonatomic, strong) UIColor *progressTintColor;
 @property(nonatomic) NSInteger roundedCorners;
 @property(nonatomic) CGFloat thicknessRatio;
 @property(nonatomic) CGFloat progress;

 @end

 @implementation DACircularProgressLayer

 @dynamic trackTintColor;
 @dynamic progressTintColor;
 @dynamic roundedCorners;
 @dynamic thicknessRatio;
 @dynamic progress;

 + (BOOL)needsDisplayForKey:(NSString *)key
 {
    return [key isEqualToString:@"progress"] ? YES : [super needsDisplayForKey:key];
 }

 - (void)drawInContext:(CGContextRef)context
 {
    CGRect rect = self.bounds;
    CGPoint centerPoint = CGPointMake(rect.size.height / 2.0f, rect.size.width / 2.0f);
    CGFloat radius = MIN(rect.size.height, rect.size.width) / 2.0f;

    CGFloat progress = MIN(self.progress, 1.0f - FLT_EPSILON);
    CGFloat radians = (progress * 2.0f * -M_PI) - M_PI_2;

    CGContextSetFillColorWithColor(context, self.trackTintColor.CGColor);
    CGMutablePathRef trackPath = CGPathCreateMutable();
    CGPathMoveToPoint(trackPath, NULL, centerPoint.x, centerPoint.y);
    CGPathAddArc(trackPath, NULL, centerPoint.x, centerPoint.y, radius, 3.0f * -M_PI_2, M_PI_2, NO);
    CGPathCloseSubpath(trackPath);
    CGContextAddPath(context, trackPath);
    CGContextFillPath(context);
    CGPathRelease(trackPath);

    if (progress > 0.0f)
    {
       CGContextSetFillColorWithColor(context, self.progressTintColor.CGColor);
       CGMutablePathRef progressPath = CGPathCreateMutable();
       CGPathMoveToPoint(progressPath, NULL, centerPoint.x, centerPoint.y);
       CGPathAddArc(progressPath, NULL, centerPoint.x, centerPoint.y, radius, 3.0f * M_PI_2, radians, NO);
       CGPathCloseSubpath(progressPath);
       CGContextAddPath(context, progressPath);
       CGContextFillPath(context);
       CGPathRelease(progressPath);
    }

    if (progress > 0.0f && self.roundedCorners)
    {
        CGFloat pathWidth = radius * self.thicknessRatio;
        CGFloat xOffset = radius * (1.0f + ((1.0f - (self.thicknessRatio / 2.0f)) * cosf(radians)));
        CGFloat yOffset = radius * (1.0f + ((1.0f - (self.thicknessRatio / 2.0f)) * sinf(radians)));
        CGPoint endPoint = CGPointMake(xOffset, yOffset);

        CGContextAddEllipseInRect(context, CGRectMake(centerPoint.x - pathWidth / 2.0f, 0.0f, pathWidth, pathWidth));
        CGContextFillPath(context);

        CGContextAddEllipseInRect(context, CGRectMake(endPoint.x - pathWidth / 2.0f, endPoint.y - pathWidth / 2.0f, pathWidth, pathWidth));
        CGContextFillPath(context);
     }

     CGContextSetBlendMode(context, kCGBlendModeClear);
     CGFloat innerRadius = radius * (1.0f - self.thicknessRatio);
     CGPoint newCenterPoint = CGPointMake(centerPoint.x - innerRadius, centerPoint.y - innerRadius);
     CGContextAddEllipseInRect(context, CGRectMake(newCenterPoint.x, newCenterPoint.y, innerRadius * 2.0f, innerRadius * 2.0f));
     CGContextFillPath(context);
 }

 @end

 @interface DACircularProgressView ()

 @end

 @implementation DACircularProgressView

 + (void) initialize
 {
    if (self != [DACircularProgressView class])
    return;

    id appearance = [self appearance];
    [appearance setTrackTintColor:[[UIColor whiteColor] colorWithAlphaComponent:0.3f]];
    [appearance setProgressTintColor:[UIColor whiteColor]];
    [appearance setBackgroundColor:[UIColor clearColor]];
    [appearance setThicknessRatio:0.3f];
    [appearance setRoundedCorners:NO];

    [appearance setIndeterminateDuration:5.0f];
    [appearance setIndeterminate:NO];
 }

 + (Class)layerClass
 {
    return [DACircularProgressLayer class];
 }

 - (DACircularProgressLayer *)circularProgressLayer
 {
    return (DACircularProgressLayer *)self.layer;
 }

 - (id)init
 {
    return [super initWithFrame:CGRectMake(0.0f, 0.0f, 40.0f, 40.0f)];
 }

 - (void)didMoveToWindow
 {   
   CGFloat windowContentsScale = self.window.screen.scale;
   self.circularProgressLayer.contentsScale = windowContentsScale;
 }

 #pragma mark - Progress

 - (CGFloat)progress
 {
    return self.circularProgressLayer.progress;
 }

 - (void)setProgress:(CGFloat)progress
 {
    [self setProgress:progress animated:NO];
 }

 - (void)setProgress:(CGFloat)progress animated:(BOOL)animated
 { 
   [self.layer removeAnimationForKey:@"indeterminateAnimation"];
   [self.circularProgressLayer removeAnimationForKey:@"progress"];

   CGFloat pinnedProgress = MIN(MAX(progress, 0.0f), 1.0f);
   if (animated)
    {
      CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"progress"];
     //  animation.duration = fabsf(self.progress - pinnedProgress); // Same duration as UIProgressView animation
     animation.duration = 10.0f;
     animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
   //  animation.fromValue = [NSNumber numberWithFloat:self.progress];
   //  animation.toValue = [NSNumber numberWithFloat:pinnedProgress];

      animation.fromValue = [NSNumber numberWithFloat:pinnedProgress];
      animation.toValue = [NSNumber numberWithFloat:self.progress];
     [self.circularProgressLayer addAnimation:animation forKey:@"progress"];
  }
  else
  {
     [self.circularProgressLayer setNeedsDisplay];
  }
  self.circularProgressLayer.progress = pinnedProgress;
}

#pragma mark - UIAppearance methods

- (UIColor *)trackTintColor
{
   return self.circularProgressLayer.trackTintColor;
}

- (void)setTrackTintColor:(UIColor *)trackTintColor
{
   self.circularProgressLayer.trackTintColor = trackTintColor;
   [self.circularProgressLayer setNeedsDisplay];
}

- (UIColor *)progressTintColor
{
  return self.circularProgressLayer.progressTintColor;
} 

- (void)setProgressTintColor:(UIColor *)progressTintColor
{
   self.circularProgressLayer.progressTintColor = progressTintColor;
   [self.circularProgressLayer setNeedsDisplay];
} 

 - (NSInteger)roundedCorners
{
   return self.roundedCorners;
}

- (void)setRoundedCorners:(NSInteger)roundedCorners
{
   self.circularProgressLayer.roundedCorners = roundedCorners;
   [self.circularProgressLayer setNeedsDisplay];
}

- (CGFloat)thicknessRatio
{
   return self.circularProgressLayer.thicknessRatio;
}

- (void)setThicknessRatio:(CGFloat)thicknessRatio
{
   self.circularProgressLayer.thicknessRatio = MIN(MAX(thicknessRatio, 0.f), 1.f);
   [self.circularProgressLayer setNeedsDisplay];
}

- (NSInteger)indeterminate
{
    CAAnimation *spinAnimation = [self.layer animationForKey:@"indeterminateAnimation"];
    return (spinAnimation == nil ? 0 : 1);
}

 - (void)setIndeterminate:(NSInteger)indeterminate
{
    if (indeterminate && !self.indeterminate)
    {
       CABasicAnimation *spinAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
       spinAnimation.byValue = [NSNumber numberWithFloat:indeterminate > 0 ? -2.0f*M_PI : 2.0f*M_PI];
       spinAnimation.duration = self.indeterminateDuration;
       spinAnimation.repeatCount = HUGE_VALF;
       [self.layer addAnimation:spinAnimation forKey:@"indeterminateAnimation"];
    }
    else
    {
      [self.layer removeAnimationForKey:@"indeterminateAnimation"];
    }
 }

 @end


i modified the example project, the output of the project is somthing like below

enter image description here

i dont think the above result is cock-wise rotation, the video is truncated at the end it will rotating in anti clock wise direction.. perfectly please check it, once again i re-posted the code. check it

open source u can download the project hear but animating clock wise modified to animate anti-clock wise in DACircularProgressView.m

Upvotes: 4

Related Questions