Search code examples
iosquartz-2d

How to draw a circle around a number (text) with effect animation?


Using Quartz 2D like Foursquare rating:

Please see link; look for the green circle, for example.

Thanks in advance.

 (void)drawRect:(CGRect)rect {

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 4.0);
    CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
    UIColor *theFillColor = [UIColor greenColor];
    CGContextSetFillColorWithColor(context, theFillColor.CGColor);
    CGRect rectangle = CGRectMake(50.0,150.0,rect.size.width-10.0,rect.size.height-10.0);
    CGContextBeginPath(context);
    CGContextAddEllipseInRect(context, rectangle);
    CGContextDrawPath(context, kCGPathFillStroke); // Or kCGPathFill
    CGRect smallRect = CGRectInset(rectangle, 40, 40);
    CGContextBeginPath(context);
    CGContextAddEllipseInRect(context, smallRect);
    CGContextDrawPath(context, kCGPathFillStroke); // Or kCGPathFill
    UIGraphicsEndImageContext();
}

drow rect is never called i am loading UI from xib ?


Solution

  • Here's a sample class I whipped up. It's not a true "animation", but gives the correct appearance of one:

    #import <UIKit/UIKit.h>
    @interface MSSNumberWithCircleView : UIView {
    
    }
    @property (assign, nonatomic) NSInteger  number;
    @property (strong, nonatomic) UIColor   *circleColor;
    @property (strong, nonatomic) UIColor   *numberColor;
    @property (strong, nonatomic) UIFont    *numberFont;
    - (id)initWithNumber:(NSInteger)number numberFont:(UIFont *)numberFont numberColor:(UIColor *)numberColor circleColor:(UIColor *)circleColor;
    @end
    
    // Header import
    #import "MSSNumberWithCircleView.h"
    // Other imports
    
    @interface MSSNumberWithCircleView () {
    
    }
    @property (assign, nonatomic) CGFloat angle;
    @property (assign, nonatomic) CGFloat animationDuration;
    @property (assign, nonatomic) CGFloat animationFrames;
    + (NSInteger)defaultNumber;
    + (UIColor *)defaultCircleColor;
    + (UIColor *)defaultNumberColor;
    + (UIFont *)defaultNumberFont;
    @end
    @implementation MSSNumberWithCircleView
    #pragma mark - Class Methods (Default Property Values)
    + (NSInteger)defaultNumber {
        return 1;
    }
    + (UIColor *)defaultCircleColor {
        static UIColor *defaultColor = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            defaultColor = [UIColor colorWithRed:136.0f/255.0f 
                                           green:190.0f/255.0f 
                                            blue:050.0f/255.0f 
                                           alpha:1.0f];
        });
        return defaultColor;
    }
    + (UIColor *)defaultNumberColor {
        static UIColor *defaultColor = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            defaultColor = [UIColor whiteColor];
        });
        return defaultColor;
    }
    + (UIFont *)defaultNumberFont {
        static UIFont *defaultFont = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            defaultFont = [UIFont fontWithName:@"HelveticaNeue-Light" size:240.0f];
        });
        return defaultFont;
    }
    #pragma mark - Initialization Methods
    - (id)init {
        self = [super init];
        if (self != nil) {
            [self defaultSetup];
        }
        return self;
    }
    - (id)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self != nil) {
            [self defaultSetup];
        }
        return self;
    }
    - (id)initWithNumber:(NSInteger)number numberFont:(UIFont *)numberFont numberColor:(UIColor *)numberColor circleColor:(UIColor *)circleColor {
        self = [super init];
        if (self != nil) {
            self.circleColor        = circleColor;
            self.numberColor        = numberColor;
            self.numberFont         = numberFont;
            self.number             = number;
        }
        return self;
    }
    - (void)defaultSetup {
        self.angle              = 0;
        self.animationDuration  = 0.4f;
        self.animationFrames    = 30;
        self.circleColor        = [MSSNumberWithCircleView defaultCircleColor];
        self.numberColor        = [MSSNumberWithCircleView defaultNumberColor];
        self.numberFont         = [MSSNumberWithCircleView defaultNumberFont];
        self.number             = [MSSNumberWithCircleView defaultNumber];
    }
    - (void)drawRect:(CGRect)rect {
    
        self.angle = self.angle + (2 * M_PI / self.animationFrames);
    
        CGContextRef context    = UIGraphicsGetCurrentContext();
    
        CGFloat circleSide      = MIN(rect.size.height, rect.size.width);
        CGFloat xOffset         = (rect.size.width  - circleSide) / 2.0f;
        CGFloat yOffset         = (rect.size.height - circleSide) / 2.0f;
    
        CGRect circleRect       = CGRectMake(xOffset, yOffset, circleSide, circleSide);
        CGPoint circleCenter    = CGPointMake(CGRectGetMidX(circleRect), CGRectGetMidY(circleRect));
    
        CGRect textRect         = CGRectInset(circleRect, circleSide * 0.1f, circleSide * 0.1f);
        CGFloat textSide        = textRect.size.width;
    
        [self.circleColor setFill];
    
        CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
        CGContextAddArc(context, circleCenter.x, circleCenter.y, circleSide / 2.0f, 0, self.angle, NO);
        CGContextFillPath(context);
    
        NSString *numberString  = [NSString stringWithFormat:@"%d", (int)self.number];
        NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
        style.alignment = NSTextAlignmentCenter;
        NSDictionary *drawDict  = @{NSFontAttributeName: self.numberFont, NSParagraphStyleAttributeName: style};
    
        CGSize size             = [numberString boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT)
                                                         options:NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin
                                                      attributes:drawDict
                                                             context:nil].size;
        CGFloat fontSize        = self.numberFont.pointSize;
        CGFloat measuredSide    = MAX(size.width, size.height);
    
        fontSize                = fontSize * (textSide / measuredSide);
    
        self.numberFont         = [self.numberFont fontWithSize:fontSize];
        drawDict                = @{NSFontAttributeName: self.numberFont, NSParagraphStyleAttributeName: style, NSForegroundColorAttributeName: self.numberColor};
    
        [numberString drawWithRect:textRect
                           options:NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin
                        attributes:drawDict
                           context:nil];
    
        if (self.angle <= (2 * M_PI)) {
            [self performSelector:@selector(setNeedsDisplay) withObject:nil afterDelay:(self.animationDuration / self.animationFrames)];
    
        } else {
            self.angle = 0;
    
        }
    }
    @end
    

    Basically, change your properties to adjust the duration of the animation. I made those values private, but you could put them in your header if you want them to be publicly accessible. Also, I pulled the defaultNumberColor from the page you posted.

    Also, this is designed to make a square (for the bounds of the circle) from the shorter side of the view you have setup in Interface Builder. It will then center the square in the view. This class is designed so that a call to -setNeedsDisplay will trigger the entire animation, which will "reset" the class for the next animation when it's done.