Search code examples
iosobjective-cpaintcode

Overlapping loader circle


I was trying to reproduce the overlapping circle Apple made for the application "Activity". (See image below).

enter image description here

If you use standard Bezier Path the staring/ending position will have effects only between 0 and 2PI. If you try, for example, to fill for 4PI (even using some shadows) you cannot simulate an overlapping loading.

How is possible to make something similar to the Apple solution to create an overlapping circle?


Solution

  • This shows the general idea--transform is definitely the way to go. You'll have to adjust the sizes, arrows, and shadows for what you need. But the view allow the tint and angle to be adjusted from Interface Builder. This should get you going.

    enter image description here

    #import <UIKit/UIKit.h>
    IB_DESIGNABLE
    
    @interface ActivityCircleView : UIView
    
    @property (nonatomic) IBInspectable CGFloat angleOfRotationInDegrees;
    
    @end
    
    
    #import "ActivityCircleView.h"
    
    @implementation ActivityCircleView
    
    - (void)setAngleOfRotation:(CGFloat)angleOfRotationInDegrees {
        _angleOfRotationInDegrees = angleOfRotationInDegrees;
        [self setNeedsDisplay];
    }
    
    - (void)drawRect:(CGRect)rect {
        [self drawActivityWithTintColor:self.tintColor angleOfRotationInDegrees:self.angleOfRotationInDegrees];
    }
    
    - (void)drawActivityWithTintColor: (UIColor*)tintColor angleOfRotationInDegrees: (CGFloat)angleOfRotationInDegrees
    {
        //// General Declarations
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef context = UIGraphicsGetCurrentContext();
    
        //// Color Declarations
        CGFloat tintColorRGBA[4];
        [tintColor getRed: &tintColorRGBA[0] green: &tintColorRGBA[1] blue: &tintColorRGBA[2] alpha: &tintColorRGBA[3]];
    
        UIColor* brighter = [UIColor colorWithRed: (tintColorRGBA[0] * 0.5 + 0.5) green: (tintColorRGBA[1] * 0.5 + 0.5) blue: (tintColorRGBA[2] * 0.5 + 0.5) alpha: (tintColorRGBA[3] * 0.5 + 0.5)];
    
        //// Gradient Declarations
        CGFloat gradientLocations[] = {0, 0.73};
        CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)@[(id)tintColor.CGColor, (id)brighter.CGColor], gradientLocations);
    
        //// Shadow Declarations
        NSShadow* shadow = [[NSShadow alloc] init];
        [shadow setShadowColor: UIColor.blackColor];
        [shadow setShadowOffset: CGSizeMake(0.1, 0.1)];
        [shadow setShadowBlurRadius: 5];
    
        //// Activity Circle
        {
            //// Circle With Overlapping Shadow
            {
                CGContextSaveGState(context);
                CGContextTranslateCTM(context, 50, 50);
                CGContextRotateCTM(context, -angleOfRotationInDegrees * M_PI / 180);
    
    
    
                //// Left Half Circle Drawing
                UIBezierPath* leftHalfCirclePath = UIBezierPath.bezierPath;
                [leftHalfCirclePath moveToPoint: CGPointMake(0, -40)];
                [leftHalfCirclePath addCurveToPoint: CGPointMake(0, -20) controlPoint1: CGPointMake(0, -40) controlPoint2: CGPointMake(0, -31.68)];
                [leftHalfCirclePath addCurveToPoint: CGPointMake(-7.44, -18.57) controlPoint1: CGPointMake(-2.63, -20) controlPoint2: CGPointMake(-5.14, -19.49)];
                [leftHalfCirclePath addCurveToPoint: CGPointMake(-20, -0) controlPoint1: CGPointMake(-14.8, -15.62) controlPoint2: CGPointMake(-20, -8.42)];
                [leftHalfCirclePath addCurveToPoint: CGPointMake(0, 20) controlPoint1: CGPointMake(-20, 11.05) controlPoint2: CGPointMake(-11.05, 20)];
                [leftHalfCirclePath addCurveToPoint: CGPointMake(0, 40) controlPoint1: CGPointMake(0, 27.41) controlPoint2: CGPointMake(0, 34.35)];
                [leftHalfCirclePath addCurveToPoint: CGPointMake(-40, -0) controlPoint1: CGPointMake(-22.09, 40) controlPoint2: CGPointMake(-40, 22.09)];
                [leftHalfCirclePath addCurveToPoint: CGPointMake(-24.08, -31.94) controlPoint1: CGPointMake(-40, -13.05) controlPoint2: CGPointMake(-33.75, -24.64)];
                [leftHalfCirclePath addLineToPoint: CGPointMake(-23.84, -32.13)];
                [leftHalfCirclePath addCurveToPoint: CGPointMake(0, -40) controlPoint1: CGPointMake(-17.18, -37.07) controlPoint2: CGPointMake(-8.93, -40)];
                [leftHalfCirclePath addLineToPoint: CGPointMake(0, -40)];
                [leftHalfCirclePath closePath];
                [tintColor setFill];
                [leftHalfCirclePath fill];
    
    
                //// Circle With Shadow Drawing
                UIBezierPath* circleWithShadowPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(-10, 20, 20, 20)];
                CGContextSaveGState(context);
                CGContextSetShadowWithColor(context, shadow.shadowOffset, shadow.shadowBlurRadius, [shadow.shadowColor CGColor]);
                [brighter setFill];
                [circleWithShadowPath fill];
                CGContextRestoreGState(context);
    
    
    
                //// Right Half Circle Drawing
                UIBezierPath* rightHalfCirclePath = UIBezierPath.bezierPath;
                [rightHalfCirclePath moveToPoint: CGPointMake(40, -0)];
                [rightHalfCirclePath addCurveToPoint: CGPointMake(0, 40) controlPoint1: CGPointMake(40, 22.09) controlPoint2: CGPointMake(22.09, 40)];
                [rightHalfCirclePath addCurveToPoint: CGPointMake(0, 20) controlPoint1: CGPointMake(0, 33.83) controlPoint2: CGPointMake(0, 27.02)];
                [rightHalfCirclePath addCurveToPoint: CGPointMake(20, -0) controlPoint1: CGPointMake(11.05, 20) controlPoint2: CGPointMake(20, 11.05)];
                [rightHalfCirclePath addCurveToPoint: CGPointMake(0, -20) controlPoint1: CGPointMake(20, -11.05) controlPoint2: CGPointMake(11.05, -20)];
                [rightHalfCirclePath addCurveToPoint: CGPointMake(0, -40) controlPoint1: CGPointMake(0, -28.3) controlPoint2: CGPointMake(0, -35.35)];
                [rightHalfCirclePath addCurveToPoint: CGPointMake(40, -0) controlPoint1: CGPointMake(22.09, -40) controlPoint2: CGPointMake(40, -22.09)];
                [rightHalfCirclePath closePath];
                CGContextSaveGState(context);
                [rightHalfCirclePath addClip];
                CGContextDrawLinearGradient(context, gradient, CGPointMake(20, -40), CGPointMake(20, 40), 0);
                CGContextRestoreGState(context);
    
    
    
                CGContextRestoreGState(context);
            }
    
    
            //// Arrow
            {
                //// Arrow Line Drawing
                UIBezierPath* arrowLinePath = UIBezierPath.bezierPath;
                [arrowLinePath moveToPoint: CGPointMake(43.5, 20.5)];
                [arrowLinePath addLineToPoint: CGPointMake(57.5, 20.5)];
                arrowLinePath.lineCapStyle = kCGLineCapRound;
    
                [UIColor.blackColor setStroke];
                arrowLinePath.lineWidth = 4;
                [arrowLinePath stroke];
    
    
                //// Arrow Head Drawing
                UIBezierPath* arrowHeadPath = UIBezierPath.bezierPath;
                [arrowHeadPath moveToPoint: CGPointMake(50.8, 14.5)];
                [arrowHeadPath addLineToPoint: CGPointMake(57.5, 20.5)];
                [arrowHeadPath addLineToPoint: CGPointMake(50.8, 26.5)];
                arrowHeadPath.lineCapStyle = kCGLineCapRound;
    
                [UIColor.blackColor setStroke];
                arrowHeadPath.lineWidth = 4;
                [arrowHeadPath stroke];
            }
        }
    
    
        //// Cleanup
        CGGradientRelease(gradient);
        CGColorSpaceRelease(colorSpace);
    }
    
    
    @end
    

    By the way, I cheated a bit and used PaintCode.