Overlapping loader circle

2019-04-11 03:17发布

问题:

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

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?

回答1:

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.

#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.



回答2:

Here's my attempt:

It's generated entirely in code (I had to use the Angle Gradient Layer to get the gradient, but the rest of the code is mine). I can easily obtain any desired angle just by applying a rotation transform:

Of course I also hard-coded all the values, because I was just trying it out, but feel free to play with it:

let con = UIGraphicsGetCurrentContext()
let c1 = UIColor(red: 0.8, green: 0.8, blue: 0.7, alpha: 1).CGColor
let c2 = UIColor(red: 0.8, green: 0.8, blue: 0.1, alpha: 1).CGColor
let outer:CGFloat = 20
let diff:CGFloat = 20
let inner:CGFloat = outer + diff
func circle(inset:CGFloat) {
    CGContextAddEllipseInRect(con, rect.rectByInsetting(
        dx: inset, dy: inset))
}

CGContextSaveGState(con)
circle(outer); circle(inner); CGContextEOClip(con)
let im = AngleGradientLayer.newImageGradientInRect(
    rect, colors:[c1,c2], locations:[0,1]).takeRetainedValue()
CGContextDrawImage(con, rect, im)
CGContextRestoreGState(con)

circle(outer)
CGContextStrokePath(con)
circle(inner)
CGContextStrokePath(con)

let (_,lower) = rect.rectsByDividing(rect.height / 2, fromEdge: .MinYEdge)
CGContextAddRect(con, lower)
CGContextEOClip(con)
CGContextSetFillColorWithColor(con, c1)

CGContextSaveGState(con)
CGContextSetShadowWithColor(con, CGSizeMake(0,3), 6, 
    UIColor.blackColor().colorWithAlphaComponent(0.7).CGColor)
let top = lower.origin.y - diff/2
let left = rect.width - diff * 2
circle(outer); circle(inner); CGContextEOClip(con)
CGContextFillEllipseInRect(con, CGRectMake(left,top,diff,diff))
CGContextRestoreGState(con)

CGContextStrokeEllipseInRect(con, CGRectMake(left,top,diff,diff))