draw embossed arc using core graphics

后端 未结 2 1461
北荒
北荒 2021-02-02 03:19

I am trying to implement a custom slider as shown in figure below.

\"enter

what I

相关标签:
2条回答
  • 2021-02-02 03:26

    check the below code for fully functional version of the above component, its working (may be its bit messy).

    #import "UIArcSlider.h"
    
    @interface UIArcSlider()
    
    @property (nonatomic) CGPoint thumbCenterPoint;
    
    #pragma mark - Init and Setup methods
    - (void)setup;
    
    #pragma mark - Thumb management methods
    - (BOOL)isPointInThumb:(CGPoint)point;
    
    #pragma mark - Drawing methods
    - (CGFloat)sliderRadius;
    - (void)drawThumbAtPoint:(CGPoint)sliderButtonCenterPoint inContext:(CGContextRef)context;
    - (CGPoint)drawTheArcTrack:(float)track atPoint:(CGPoint)center withRadius:(CGFloat)radius AndColor:(Boolean) isWhite inContext:(CGContextRef)context;
    - (CGPoint)drawTheLineTrack:(float)track withColor:(Boolean) isWhite inContext:(CGContextRef)context;
    
    @end
    
    #pragma mark -
    @implementation UIArcSlider
    
    @synthesize sliderStyle = _sliderStyle;
    - (void)setSliderStyle:(UISliderStyle)sliderStyle {
        if (sliderStyle != _sliderStyle) {
            _sliderStyle = sliderStyle;
            [self setNeedsDisplay];
        }
    }
    
    @synthesize value = _value;
    - (void)setValue:(float)value {
        if (value != _value) {
            if (value > self.maximumValue) { value = self.maximumValue; }
            if (value < self.minimumValue) { value = self.minimumValue; }
            _value = value;
            [self setNeedsDisplay];
            [self sendActionsForControlEvents:UIControlEventValueChanged];
        }
    }
    @synthesize minimumValue = _minimumValue;
    - (void)setMinimumValue:(float)minimumValue {
        if (minimumValue != _minimumValue) {
            _minimumValue = minimumValue;
            if (self.maximumValue < self.minimumValue)  { self.maximumValue = self.minimumValue; }
            if (self.value < self.minimumValue)         { self.value = self.minimumValue; }
        }
    }
    @synthesize maximumValue = _maximumValue;
    - (void)setMaximumValue:(float)maximumValue {
        if (maximumValue != _maximumValue) {
            _maximumValue = maximumValue;
            if (self.minimumValue > self.maximumValue)  { self.minimumValue = self.maximumValue; }
            if (self.value > self.maximumValue)         { self.value = self.maximumValue; }
        }
    }
    
    @synthesize thumbCenterPoint = _thumbCenterPoint;
    
    /** @name Init and Setup methods */
    #pragma mark - Init and Setup methods
    - (id)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self setup];
        } 
        return self;
    }
    - (void)awakeFromNib {
        [self setup];
    }
    
    - (void)setup {
        self.value = 0.0;
        self.minimumValue = 0.0;
        self.maximumValue = 100.0;
        self.thumbCenterPoint = CGPointZero;
    
        UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureHappened:)];
        [self addGestureRecognizer:tapGestureRecognizer];
    
        UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureHappened:)];
        panGestureRecognizer.maximumNumberOfTouches = panGestureRecognizer.minimumNumberOfTouches;
        [self addGestureRecognizer:panGestureRecognizer];
    }
    
    /** @name Drawing methods */
    #pragma mark - Drawing methods
    #define kLineWidth 22.0
    #define kThumbRadius 20.0
    #define kPadding 20.0
    
    - (CGFloat)sliderRadius {
        CGFloat radius = self.bounds.size.width;
        radius -= MAX(kLineWidth + kPadding, kThumbRadius  + kPadding); 
        return radius;
    }
    
    -(CGFloat)sliderWidth {    
        return self.bounds.size.width - kThumbRadius*2;
    }
    
    - (void)drawThumbAtPoint:(CGPoint)sliderButtonCenterPoint inContext:(CGContextRef)context {
        UIGraphicsPushContext(context);
        CGContextBeginPath(context);    
    
        CGContextMoveToPoint(context, sliderButtonCenterPoint.x, sliderButtonCenterPoint.y);
    
        CGRect rect = CGRectMake(sliderButtonCenterPoint.x - kThumbRadius, sliderButtonCenterPoint.y - kThumbRadius, kThumbRadius*2, kThumbRadius*2);
        CGImageRef imageRef = [UIImage imageNamed:@"circle25.png"].CGImage;
    
        CGContextDrawImage(context, rect, imageRef);
    
        UIGraphicsPopContext();        
    }
    
    - (CGPoint)drawTheArcTrack:(float)track atPoint:(CGPoint)center withRadius:(CGFloat)radius AndColor:(Boolean) isWhite inContext:(CGContextRef)context 
    {
        static CGFloat const kArcThickness = kLineWidth;
        CGPoint arcCenter = center;
        CGFloat arcRadius = radius;
    
        float angleFromTrack = translateValueFromSourceIntervalToDestinationInterval(track, self.minimumValue, self.maximumValue, 0, M_PI/3);// 2*M_PI
    
        CGFloat startAngle = (4*M_PI)/3;
        CGFloat endAngle = startAngle + angleFromTrack;
    
        UIBezierPath *arc = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:arcRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
    
        CGPathRef shape = CGPathCreateCopyByStrokingPath(arc.CGPath, NULL, kArcThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f);
        CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape);
        CGPathAddRect(shapeInverse, NULL, CGRectInfinite);
    
        CGPoint arcEndPoint = [arc currentPoint];
    
        CGContextBeginPath(context);
        CGContextAddPath(context, shape);
    
        if (isWhite) {
            CGContextSetFillColorWithColor(context, [UIColor colorWithWhite:.9 alpha:1].CGColor);
        } else {//70,172, 220
            CGContextSetFillColorWithColor(context, [UIColor colorWithRed:70/255.0 green:172/255.0 blue:220/255.0 alpha:1].CGColor);
        }    
    
        CGContextFillPath(context);
    
        CGContextSaveGState(context); {
            CGContextBeginPath(context);
            CGContextAddPath(context, shape);
            CGContextClip(context);
            if (isWhite) {
                CGContextSetShadowWithColor(context, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor);
            } else {
                CGContextSetShadowWithColor(context, CGSizeZero, 7, [UIColor colorWithRed:85/255.0 green:183/255.0 blue:230/255.0 alpha:.25].CGColor);
            }       
            CGContextBeginPath(context);
            CGContextAddPath(context, shapeInverse);
            CGContextFillPath(context);
        } CGContextRestoreGState(context);
    
        if (isWhite) {
            CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:.75 alpha:1].CGColor);
        } else {
            CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:62/255.0 green:135/255.0 blue:169/255.0 alpha:1].CGColor);
        }
    
        CGContextSetLineWidth(context, 1);
        CGContextSetLineJoin(context, kCGLineCapRound);
        CGContextBeginPath(context);
        CGContextAddPath(context, shape);
        CGContextStrokePath(context);
    
        CGPathRelease(shape);
        CGPathRelease(shapeInverse);        
    
        return arcEndPoint;    
    }
    
    
    - (CGPoint)drawTheLineTrack:(float)track withColor:(Boolean) isWhite inContext:(CGContextRef)context
    {        
        CGFloat sliderWidth = [self sliderWidth];
        CGFloat xStart = self.bounds.origin.x + (self.bounds.size.width - sliderWidth)/2;
        CGFloat lineRectX = (self.bounds.size.width - sliderWidth)/2;
        CGFloat lineThickness = kLineWidth/2;
        CGFloat lineRectY = (self.bounds.size.height - lineThickness)/2;
    
        CGFloat xPoint = translateValueToPoint(track, xStart, self.maximumValue, sliderWidth);
        sliderWidth = xPoint;
    
        CGRect lineRect = CGRectMake(lineRectX, lineRectY, sliderWidth, lineThickness);
        UIBezierPath *line = [UIBezierPath bezierPathWithRoundedRect:lineRect cornerRadius:lineThickness/2];
    
        CGPathRef shape = CGPathCreateCopyByStrokingPath(line.CGPath, NULL, lineThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f);
        CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape);
        CGPathAddRect(shapeInverse, NULL, CGRectInfinite);
    
        CGPoint arcEndPoint = CGPointMake(lineRectX + xPoint, lineRectY + (lineThickness/2));
    
        CGContextBeginPath(context);
        CGContextAddPath(context, shape);
    
        if (isWhite) {
            CGContextSetFillColorWithColor(context, [UIColor colorWithWhite:.9 alpha:1].CGColor);
        } else {//70,172, 220
            CGContextSetFillColorWithColor(context, [UIColor colorWithRed:70/255.0 green:172/255.0 blue:220/255.0 alpha:1].CGColor);
        }    
    
        CGContextFillPath(context);
    
        CGContextSaveGState(context); {
            CGContextBeginPath(context);
            CGContextAddPath(context, shape);
            CGContextClip(context);
            if (isWhite) {
                CGContextSetShadowWithColor(context, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor);
            } else {
                CGContextSetShadowWithColor(context, CGSizeZero, 7, [UIColor colorWithRed:85/255.0 green:183/255.0 blue:230/255.0 alpha:.25].CGColor);
            }       
            CGContextBeginPath(context);
            CGContextAddPath(context, shapeInverse);
            CGContextFillPath(context);
        } CGContextRestoreGState(context);
    
        if (isWhite) {
            CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:.75 alpha:1].CGColor);
        } else {
            CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:62/255.0 green:135/255.0 blue:169/255.0 alpha:1].CGColor);
        }
    
        CGRect outlineRect = CGRectMake(lineRectX - (lineThickness/2), lineRectY - (lineThickness/2), sliderWidth + lineThickness, lineThickness * 2);
        UIBezierPath *outline = [UIBezierPath bezierPathWithRoundedRect:outlineRect cornerRadius:lineThickness];
        CGContextSetLineWidth(context, 1);
        CGContextSetLineJoin(context, kCGLineCapSquare);
        CGContextBeginPath(context);
        CGContextAddPath(context, outline.CGPath);
        CGContextStrokePath(context);
    
        CGPathRelease(shape);
        CGPathRelease(shapeInverse);        
    
        return arcEndPoint;    
    }
    
    - (void)drawRect:(CGRect)rect {
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGPoint middlePoint;
    
        switch (self.sliderStyle) {
            case UISliderStyleLine:
    
                [self drawTheLineTrack:self.maximumValue withColor:YES inContext:context];
                self.thumbCenterPoint = [self drawTheLineTrack:self.value withColor:NO inContext:context];
    
                [self drawThumbAtPoint:self.thumbCenterPoint inContext:context];
                break;
    
            case UISliderStyleArc:
            default:
                middlePoint.x = self.bounds.origin.x + self.bounds.size.width/2;
                middlePoint.y = self.bounds.origin.y + self.bounds.size.width;
    
                CGFloat radius = [self sliderRadius];                
                [self drawTheArcTrack:self.maximumValue atPoint:middlePoint withRadius:radius AndColor:YES inContext:context];
    
                self.thumbCenterPoint = [self drawTheArcTrack:self.value atPoint:middlePoint withRadius:radius AndColor:NO inContext:context];  
    
                [self drawThumbAtPoint:self.thumbCenterPoint inContext:context];
                break;
        }
    }
    
    /** @name Thumb management methods */
    #pragma mark - Thumb management methods
    - (BOOL)isPointInThumb:(CGPoint)point {
        CGRect thumbTouchRect = CGRectMake(self.thumbCenterPoint.x - kThumbRadius, self.thumbCenterPoint.y - kThumbRadius, kThumbRadius*2, kThumbRadius*2);
        return CGRectContainsPoint(thumbTouchRect, point);
    }
    
    /** @name UIGestureRecognizer management methods */
    #pragma mark - UIGestureRecognizer management methods
    - (void)panGestureHappened:(UIPanGestureRecognizer *)panGestureRecognizer {
        CGPoint tapLocation = [panGestureRecognizer locationInView:self];
        switch (panGestureRecognizer.state) {
            case UIGestureRecognizerStateChanged: {
    
                CGFloat radius;
                CGPoint sliderCenter;
                CGPoint sliderStartPoint;
                CGFloat angle;
    
    
                CGFloat xStart;
                CGFloat maximumValue;
                CGFloat sliderWidth;
                CGFloat point;
    
                switch (self.sliderStyle) {
                    case UISliderStyleLine:
    
                        sliderWidth = [self sliderWidth];
                        xStart = self.bounds.origin.x + (self.bounds.size.width - sliderWidth)/2;
                        maximumValue = self.maximumValue;
                        point = tapLocation.x;
    
                        self.value = translatePointToValue(point, xStart, maximumValue, sliderWidth);
    
                        break;
    
                    case UISliderStyleArc:
                    default:
    
                        radius = [self sliderRadius];
                        sliderCenter = CGPointMake(self.bounds.size.width/2, self.bounds.size.width);
                        sliderStartPoint = CGPointMake(sliderCenter.x - (radius * 1.15), sliderCenter.y - (radius * 2.1) );
                        angle = angleBetweenThreePoints(sliderCenter, sliderStartPoint, tapLocation);
    
                        if (angle < 0) {
                            angle = -angle;
                        }
                        else {
                            angle = 0.0;
                            //angle = M_PI/3 - angle;
                        }
    
                        self.value = translateValueFromSourceIntervalToDestinationInterval(angle, 0, M_PI/3, self.minimumValue, self.maximumValue);
                        break;
                }
            }
            default:
                break;
        }
    }
    - (void)tapGestureHappened:(UITapGestureRecognizer *)tapGestureRecognizer {
        if (tapGestureRecognizer.state == UIGestureRecognizerStateEnded) {
            CGPoint tapLocation = [tapGestureRecognizer locationInView:self];
            if ([self isPointInThumb:tapLocation]) {
                // do something on tap
            }
            else {
            }
        }
    }
    
    @end
    
    /** @name Utility Functions */
    #pragma mark - Utility Functions
    float translateValueFromSourceIntervalToDestinationInterval(float sourceValue, float sourceIntervalMinimum, float sourceIntervalMaximum, float destinationIntervalMinimum, float destinationIntervalMaximum) {
        float a, b, destinationValue;
    
        a = (destinationIntervalMaximum - destinationIntervalMinimum) / (sourceIntervalMaximum - sourceIntervalMinimum);
        b = destinationIntervalMaximum - a*sourceIntervalMaximum;
    
        destinationValue = a*sourceValue + b;
    
        return destinationValue;
    }
    
    float translateValueToPoint(float value, float xStart, float maximum, float width)
    {
        float point = (width * value) / maximum;
    //    
    //    point = xStart + point;
    //    
        return point;   
    }
    
    float translatePointToValue(float point, float xStart, float maximum, float width)
    {
        float value = (point) * maximum;
        value = value / width;
    
        return value;
    }
    
    
    CGFloat angleBetweenThreePoints(CGPoint centerPoint, CGPoint p1, CGPoint p2) {
        CGPoint v1 = CGPointMake(p1.x - centerPoint.x, p1.y - centerPoint.y);
        CGPoint v2 = CGPointMake(p2.x - centerPoint.x, p2.y - centerPoint.y);
    
        CGFloat angle = atan2f(v2.x*v1.y - v1.x*v2.y, v1.x*v2.x + v1.y*v2.y);
    
        return angle;
    }
    

    You'll require the thumb image as well enter image description here

    0 讨论(0)
  • 2021-02-02 03:48

    Unless you are going to be changing the shape dynamically, you would probably be better off just creating the image in an image editor. I know it's easy to create that effect in Photoshop, Illustrator, or Fireworks.

    That said, drawing an inner shadow like that with Core Graphics requires several steps:

    1. Clip to the shape (using e.g. CGContextClip or CGContextClipToMask).
    2. Make a path or mask of everything but the shape.
    3. Set your shadow parameters (using CGContextSetShadowWithColor).
    4. Fill the path or mask from step 2. This casts a shadow inside the shape, and only the shadow is drawn because you clipped to the shape in step 1.

    If you do all of that correctly, you can get a nice result like this:

    screen shot of arc with inner shadow

    Here's the code I wrote to draw that. I wrote it in the drawRect: of a custom view subclass, but you can easily use this code to draw into any graphics context.

    - (void)drawRect:(CGRect)rect {
        CGContextRef gc = UIGraphicsGetCurrentContext();
    

    First, I create a path that's just an arc:

        static CGFloat const kArcThickness = 20.0f;
        CGRect arcBounds = CGRectInset(self.bounds, 10.0f, 10.0f);
        CGPoint arcCenter = CGPointMake(CGRectGetMidX(arcBounds), CGRectGetMidY(arcBounds));
        CGFloat arcRadius = 0.5f * (MIN(arcBounds.size.width, arcBounds.size.height) - kArcThickness);
        UIBezierPath *arc = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:arcRadius startAngle:-M_PI / 3.0 endAngle:-2.0 * M_PI / 3.0 clockwise:NO];
    

    Next, I ask Core Graphics to make a new path that is the outline of the arc path. Note how I ask it for a stroke width of kArcThickness and round line caps:

        CGPathRef shape = CGPathCreateCopyByStrokingPath(arc.CGPath, NULL, kArcThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f);
    

    I also need the inverse of that path: a path that includes every point except the points in shape. It so happens (although I don't think it's documented) that CGContextCreateCopyByStrokingPath and CGPathAddRect draw in opposite directions. So if I copy shape and draw an enormous rectangle around it, the non-zero winding rule means that the new path will be the inverse of shape:

        CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape);
        CGPathAddRect(shapeInverse, NULL, CGRectInfinite);
    

    Now I can actually start drawing. First, I'll fill in the shape with a light gray color:

        CGContextBeginPath(gc);
        CGContextAddPath(gc, shape);
        CGContextSetFillColorWithColor(gc, [UIColor colorWithWhite:.9 alpha:1].CGColor);
        CGContextFillPath(gc);
    

    Next I actually perform the four steps I listed above. I have to save the graphics state so I can undo the clipping and shadow parameters when I'm done.

        CGContextSaveGState(gc); {
    

    Step 1: clip to the shape:

            CGContextBeginPath(gc);
            CGContextAddPath(gc, shape);
            CGContextClip(gc);
    

    Step 2: Well, I did this step already when I created shapeInverse.

    Step 3: I set the shadow parameters:

            CGContextSetShadowWithColor(gc, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor);
    

    Step 4: I fill the inverse shape from step 2:

            CGContextBeginPath(gc);
            CGContextAddPath(gc, shapeInverse);
            CGContextFillPath(gc);
    

    Now I restore the graphics state, which specifically restores the clipping path and unsets the shadow parameters.

        } CGContextRestoreGState(gc);
    

    Finally, I'll stroke shape with a light gray to make the edge crisper:

        CGContextSetStrokeColorWithColor(gc, [UIColor colorWithWhite:.75 alpha:1].CGColor);
        CGContextSetLineWidth(gc, 1);
        CGContextSetLineJoin(gc, kCGLineCapRound);
        CGContextBeginPath(gc);
        CGContextAddPath(gc, shape);
        CGContextStrokePath(gc);
    

    Of course I clean up when I'm done:

        CGPathRelease(shape);
        CGPathRelease(shapeInverse);
    }
    

    For more complex shapes, you can look at my answer here and my answer here.

    Here's all the code together for easy copying:

    - (void)drawRect:(CGRect)rect {
        CGContextRef gc = UIGraphicsGetCurrentContext();
    
        static CGFloat const kArcThickness = 20.0f;
        CGRect arcBounds = CGRectInset(self.bounds, 10.0f, 10.0f);
        CGPoint arcCenter = CGPointMake(CGRectGetMidX(arcBounds), CGRectGetMidY(arcBounds));
        CGFloat arcRadius = 0.5f * (MIN(arcBounds.size.width, arcBounds.size.height) - kArcThickness);
        UIBezierPath *arc = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:arcRadius startAngle:-M_PI / 3.0 endAngle:-2.0 * M_PI / 3.0 clockwise:NO];
        CGPathRef shape = CGPathCreateCopyByStrokingPath(arc.CGPath, NULL, kArcThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f);
        CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape);
        CGPathAddRect(shapeInverse, NULL, CGRectInfinite);
    
        CGContextBeginPath(gc);
        CGContextAddPath(gc, shape);
        CGContextSetFillColorWithColor(gc, [UIColor colorWithWhite:.9 alpha:1].CGColor);
        CGContextFillPath(gc);
    
        CGContextSaveGState(gc); {
            CGContextBeginPath(gc);
            CGContextAddPath(gc, shape);
            CGContextClip(gc);
            CGContextSetShadowWithColor(gc, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor);
            CGContextBeginPath(gc);
            CGContextAddPath(gc, shapeInverse);
            CGContextFillPath(gc);
        } CGContextRestoreGState(gc);
    
        CGContextSetStrokeColorWithColor(gc, [UIColor colorWithWhite:.75 alpha:1].CGColor);
        CGContextSetLineWidth(gc, 1);
        CGContextSetLineJoin(gc, kCGLineCapRound);
        CGContextBeginPath(gc);
        CGContextAddPath(gc, shape);
        CGContextStrokePath(gc);
    
        CGPathRelease(shape);
        CGPathRelease(shapeInverse);
    }
    
    0 讨论(0)
提交回复
热议问题