I have a scroll view (gray) with a zooming view inside (orange). The problem is if I zoom this view the red shape drawn on it gets zoomed too including lines width and blue squares size. What I want is to keep constant lines width and blue squares size (like on first picture) scaling just the area of the shape itself according to zoom level (drawn text is just for reference, I don't care about its size)
before zoom
after zoom
view controller
#import "ViewController.h"
#import "ZoomingView.h"
#interface ViewController ()
#property (strong, nonatomic) IBOutlet UIScrollView *scrollView;
#end
#implementation ViewController
{
ZoomingView *_zoomingView;
}
- (void)viewDidLayoutSubviews
{
[self setup];
}
- (void)setup
{
CGFloat kViewSize = self.scrollView.frame.size.width - 40;
self.scrollView.minimumZoomScale = 1;
self.scrollView.maximumZoomScale = 10;
self.scrollView.delegate = self;
self.scrollView.contentSize = self.scrollView.bounds.size;
_zoomingView = [[ZoomingView alloc] initWithFrame:
CGRectMake((self.scrollView.frame.size.width - kViewSize) / 2,
(self.scrollView.frame.size.height - kViewSize) / 2,
kViewSize,
kViewSize)];
[self.scrollView addSubview:_zoomingView];
}
#pragma mark - UIScrollViewDelegate
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return _zoomingView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
// zooming view position fix
UIView *zoomView = [scrollView.delegate viewForZoomingInScrollView:scrollView];
CGRect zvf = zoomView.frame;
if (zvf.size.width < scrollView.bounds.size.width) {
zvf.origin.x = (scrollView.bounds.size.width - zvf.size.width) / 2.0f;
} else {
zvf.origin.x = 0.0;
}
if (zvf.size.height < scrollView.bounds.size.height) {
zvf.origin.y = (scrollView.bounds.size.height - zvf.size.height) / 2.0f;
} else {
zvf.origin.y = 0.0;
}
zoomView.frame = zvf;
[_zoomingView updateWithZoomScale:scrollView.zoomScale];
}
#end
zooming view
#import "ZoomingView.h"
#implementation ZoomingView
{
CGFloat _zoomScale;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)setup
{
self.backgroundColor = [UIColor orangeColor];
_zoomScale = 1;
}
- (void)drawRect:(CGRect)rect
{
const CGFloat kPointSize = 10;
NSArray *points = #[[NSValue valueWithCGPoint:CGPointMake(30, 30)],
[NSValue valueWithCGPoint:CGPointMake(200, 40)],
[NSValue valueWithCGPoint:CGPointMake(180, 200)],
[NSValue valueWithCGPoint:CGPointMake(70, 180)]];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 1);
// points
[[UIColor blueColor] setStroke];
for (NSValue *value in points) {
CGPoint point = [value CGPointValue];
CGContextStrokeRect(context, CGRectMake(point.x - kPointSize / 2,
point.y - kPointSize / 2,
kPointSize,
kPointSize));
}
// lines
[[UIColor redColor] setStroke];
for (NSUInteger i = 0; i < points.count; i++) {
CGPoint point = [points[i] CGPointValue];
if (i == 0) {
CGContextMoveToPoint(context, point.x, point.y);
} else {
CGContextAddLineToPoint(context, point.x, point.y);
}
}
CGContextClosePath(context);
CGContextStrokePath(context);
// text
NSAttributedString *str = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%f", _zoomScale] attributes:#{NSFontAttributeName : [UIFont systemFontOfSize:12]}];
[str drawAtPoint:CGPointMake(5, 5)];
}
- (void)updateWithZoomScale:(CGFloat)zoomScale
{
_zoomScale = zoomScale;
[self setNeedsDisplay];
}
#end
EDIT
Based on proposed solution (which works for sure) I was interested if I could make it work using my drawRect routine and Core Graphics methods.
So I changed my code this way, applying proposed scaling and contentsScale approach from this answer. As a result, without contentsScale drawing looks very blurry and with it much better, but a light blurriness persists anyway.
So the approach with layers gives the best result, although I don't get why.
- (void)drawRect:(CGRect)rect
{
const CGFloat kPointSize = 10;
NSArray *points = #[[NSValue valueWithCGPoint:CGPointMake(30, 30)],
[NSValue valueWithCGPoint:CGPointMake(200, 40)],
[NSValue valueWithCGPoint:CGPointMake(180, 200)],
[NSValue valueWithCGPoint:CGPointMake(70, 180)]];
CGFloat scaledPointSize = kPointSize * (1.0 / _zoomScale);
CGFloat lineWidth = 1.0 / _zoomScale;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, lineWidth);
// points
[[UIColor blueColor] setStroke];
for (NSValue *value in points) {
CGPoint point = [value CGPointValue];
CGContextStrokeRect(context, CGRectMake(point.x - scaledPointSize / 2,
point.y - scaledPointSize / 2,
scaledPointSize,
scaledPointSize));
}
// lines
[[UIColor redColor] setStroke];
for (NSUInteger i = 0; i < points.count; i++) {
CGPoint point = [points[i] CGPointValue];
if (i == 0) {
CGContextMoveToPoint(context, point.x, point.y);
} else {
CGContextAddLineToPoint(context, point.x, point.y);
}
}
CGContextClosePath(context);
CGContextStrokePath(context);
// text
NSAttributedString *str = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%f", _zoomScale] attributes:#{NSFontAttributeName : [UIFont systemFontOfSize:12]}];
[str drawAtPoint:CGPointMake(5, 5)];
}
- (void)updateWithZoomScale:(CGFloat)zoomScale
{
_zoomScale = zoomScale;
[self setNeedsDisplay];
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithBool:YES]
forKey:kCATransactionDisableActions];
self.layer.contentsScale = zoomScale;
[CATransaction commit];
}
You may be better off putting your boxes and line-shape on CAShapeLayers, where you can update the line-width based on the zoom scale.
You only need to create and define your line-shape once. For your boxes, though, you'll need to re-create the path when you change the zoom (to keep the width/height of the boxes at a constant non-zoomed point size.
Give this a try. You should be able to simply replace your current ZoomingView.m class - no changes to the view controller necessary.
//
// ZoomingView.m
//
// modified by Don Mag
//
#import "ZoomingView.h"
#interface ZoomingView()
#property (strong, nonatomic) CAShapeLayer *shapeLayer;
#property (strong, nonatomic) CAShapeLayer *boxesLayer;
#property (strong, nonatomic) NSArray *points;
#end
#implementation ZoomingView
{
CGFloat _zoomScale;
CGFloat _kPointSize;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)setup
{
self.backgroundColor = [UIColor orangeColor];
_points = #[[NSValue valueWithCGPoint:CGPointMake(30, 30)],
[NSValue valueWithCGPoint:CGPointMake(200, 40)],
[NSValue valueWithCGPoint:CGPointMake(180, 200)],
[NSValue valueWithCGPoint:CGPointMake(70, 180)]];
_zoomScale = 1;
_kPointSize = 10.0;
// create and setup boxes layer
_boxesLayer = [CAShapeLayer new];
[self.layer addSublayer:_boxesLayer];
_boxesLayer.strokeColor = [UIColor redColor].CGColor;
_boxesLayer.fillColor = [UIColor clearColor].CGColor;
_boxesLayer.lineWidth = 1.0;
_boxesLayer.frame = self.bounds;
// create and setup shape layer
_shapeLayer = [CAShapeLayer new];
[self.layer addSublayer:_shapeLayer];
_shapeLayer.strokeColor = [UIColor greenColor].CGColor;
_shapeLayer.fillColor = [UIColor clearColor].CGColor;
_shapeLayer.lineWidth = 1.0;
_shapeLayer.frame = self.bounds;
// new path for shape
UIBezierPath *thePath = [UIBezierPath new];
for (NSValue *value in _points) {
CGPoint point = [value CGPointValue];
if ([value isEqualToValue:_points.firstObject]) {
[thePath moveToPoint:point];
} else {
[thePath addLineToPoint:point];
}
}
[thePath closePath];
[_shapeLayer setPath:thePath.CGPath];
// trigger the boxes update
[self updateWithZoomScale:_zoomScale];
}
- (void)drawRect:(CGRect)rect
{
// text
NSAttributedString *str = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%f", _zoomScale] attributes:#{NSFontAttributeName : [UIFont systemFontOfSize:12]}];
[str drawAtPoint:CGPointMake(5, 5)];
}
- (void)updateWithZoomScale:(CGFloat)zoomScale
{
_zoomScale = zoomScale;
CGFloat scaledPointSize = _kPointSize * (1.0 / zoomScale);
// create a path for the boxes
// needs to be done here, because the width/height of the boxes
// must change with the scale
UIBezierPath *thePath = [UIBezierPath new];
for (NSValue *value in _points) {
CGPoint point = [value CGPointValue];
CGRect r = CGRectMake(point.x - scaledPointSize / 2.0,
point.y - scaledPointSize / 2.0,
scaledPointSize,
scaledPointSize);
[thePath appendPath:[UIBezierPath bezierPathWithRect:r]];
}
[_boxesLayer setPath:thePath.CGPath];
_boxesLayer.lineWidth = 1.0 / zoomScale;
_shapeLayer.lineWidth = 1.0 / zoomScale;
[self setNeedsDisplay];
}
#end
Results:
Note: Should go without saying, but... This is intended to be a starting point for you to work with, not "production code."
Related
I need to replace the default annotation view with my custom annotation view.
I need the do following things:
Custom Annotation view with an image view embedded in it.
A view below it which contains a label in it.
For more clarification see the image:
In the above image I need to place an image view in the white space which you can see in the image in circular form, next I also need to add a view which contains a label on which I can set any text like me, friends, etc...
So, for this I searched number of questions on stack overflow but didn't got my answer. I don't want it on call out, I just want it simply as annotation when map is rendered. I have tried to make a custom class for this but not getting any idea how to deal with this.
Any help will be highly appreciated
You could just create your own annotation view:
#import MapKit;
#interface CustomAnnotationView : MKAnnotationView
#end
#interface CustomAnnotationView ()
#property (nonatomic) CGSize textSize;
#property (nonatomic) CGSize textBubbleSize;
#property (nonatomic, weak) UILabel *label;
#property (nonatomic) CGFloat lineWidth;
#property (nonatomic) CGFloat pinRadius;
#property (nonatomic) CGFloat pinHeight;
#property (nonatomic, strong) UIBezierPath *pinPath;
#property (nonatomic, strong) UIBezierPath *textBubblePath;
#end
#implementation CustomAnnotationView
- (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self) {
self.lineWidth = 1.0;
self.pinHeight = 40;
self.pinRadius = 15;
UILabel *label = [[UILabel alloc] init];
label.textAlignment = NSTextAlignmentCenter;
label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCallout];
label.textColor = [UIColor whiteColor];
[self addSubview:label];
self.label = label;
[self adjustLabelWidth:annotation];
self.opaque = false;
}
return self;
}
- (void)setAnnotation:(id<MKAnnotation>)annotation {
[super setAnnotation:annotation];
if (annotation) [self adjustLabelWidth:annotation];
}
- (void)adjustLabelWidth:(id<MKAnnotation>)annotation {
NSString *title = [annotation title];
NSDictionary *attributes = #{NSFontAttributeName : self.label.font};
self.textSize = [title sizeWithAttributes:attributes];
CGFloat delta = self.textSize.height * (1.0 - sinf(M_PI_4)) * 0.55;
self.textBubbleSize = CGSizeMake(self.textSize.width + delta * 2, self.textSize.height + delta * 2);
self.label.frame = CGRectMake(0, self.pinHeight, self.textBubbleSize.width, self.textBubbleSize.height);
self.label.text = title;
self.frame = CGRectMake(0, 0, self.textBubbleSize.width, self.pinHeight + self.textBubbleSize.height);
self.centerOffset = CGPointMake(0, self.frame.size.height / 2.0 - self.pinHeight);
}
- (void)drawRect:(CGRect)rect {
CGFloat radius = self.pinRadius - self.lineWidth / 2.0;
CGPoint startPoint = CGPointMake(self.textBubbleSize.width / 2.0, self.pinHeight);
CGPoint center = CGPointMake(self.textBubbleSize.width / 2, self.pinRadius);
CGPoint nextPoint;
// pin
self.pinPath = [UIBezierPath bezierPath];
[self.pinPath moveToPoint:startPoint];
nextPoint = CGPointMake(self.textBubbleSize.width / 2 - radius, self.pinRadius);
[self.pinPath addCurveToPoint:nextPoint
controlPoint1:CGPointMake(startPoint.x, startPoint.y - (startPoint.y - nextPoint.y) / 2.0)
controlPoint2:CGPointMake(nextPoint.x, nextPoint.y + (startPoint.y - nextPoint.y) / 2.0)];
[self.pinPath addArcWithCenter:center radius:radius startAngle:M_PI endAngle:0 clockwise:TRUE];
nextPoint = startPoint;
startPoint = self.pinPath.currentPoint;
[self.pinPath addCurveToPoint:nextPoint
controlPoint1:CGPointMake(startPoint.x, startPoint.y - (startPoint.y - nextPoint.y) / 2.0)
controlPoint2:CGPointMake(nextPoint.x, nextPoint.y + (startPoint.y - nextPoint.y) / 2.0)];
[[UIColor blackColor] setStroke];
[[UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:0.8] setFill];
self.pinPath.lineWidth = self.lineWidth;
[self.pinPath fill];
[self.pinPath stroke];
[self.pinPath closePath];
// bubble around label
if ([self.annotation.title length] > 0) {
self.textBubblePath = [UIBezierPath bezierPath];
CGRect bubbleRect = CGRectInset(CGRectMake(0, self.pinHeight, self.textBubbleSize.width, self.textBubbleSize.height), self.lineWidth / 2, self.lineWidth / 2);
self.textBubblePath = [UIBezierPath bezierPathWithRoundedRect:bubbleRect
cornerRadius:bubbleRect.size.height / 2];
self.textBubblePath.lineWidth = self.lineWidth;
[self.textBubblePath fill];
[self.textBubblePath stroke];
} else {
self.textBubblePath = nil;
}
// center white dot
self.pinPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius / 3.0 startAngle:0 endAngle:M_PI * 2.0 clockwise:TRUE];
self.pinPath.lineWidth = self.lineWidth;
[[UIColor whiteColor] setFill];
[self.pinPath fill];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event {
if ([self.pinPath containsPoint:point] || [self.textBubblePath containsPoint:point])
return self;
return nil;
}
#end
That yields something like:
Clearly, you can customize this to your heart's content, but it illustrates the basic idea: Write a MKAnnotationView subclass that overrides initWithAnnotation:reuseIdentifier: and implement your own drawRect.
I would like to implement the custom circular slider for IOS. I refer to the EFCircularSlider to create my own one. When it comes to the customization such as filling in the translucent color insider the circular slider , i have found that this line is not working. Would you please tell me are there any other alternatives?
CGContextSetFillColorWithColor(ctx, [UIColor greenColor].CGColor );
The below is my code (EFCircularSlider.m)
#import "EFCircularSlider.h"
#import <QuartzCore/QuartzCore.h>
#import <CoreImage/CoreImage.h>
#define kDefaultFontSize 14.0f;
#define ToRad(deg) ( (M_PI * (deg)) / 180.0 )
#define ToDeg(rad) ( (180.0 * (rad)) / M_PI )
#define SQR(x) ( (x) * (x) )
#interface EFCircularSlider (private)
#property (readonly, nonatomic) CGFloat radius;
#end
#implementation EFCircularSlider {
int angle;
int fixedAngle;
NSMutableDictionary* labelsWithPercents;
NSArray* labelsEvenSpacing;
}
- (void)defaults {
// Defaults
_maximumValue = 100.0f;
_minimumValue = 0.0f;
_currentValue = 0.0f;
_lineWidth = 5;
_lineRadiusDisplacement = 0;
_unfilledColor = [UIColor lightGrayColor];
_filledColor = [UIColor blueColor];
_handleColor = _filledColor;
_labelFont = [UIFont systemFontOfSize:10.0f];
_snapToLabels = NO;
_handleType = EFSemiTransparentWhiteCircle;
_labelColor = [UIColor redColor];
_labelDisplacement = 2;
self.backgroundColor = [UIColor clearColor];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self defaults];
[self setFrame:frame];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self=[super initWithCoder:aDecoder])){
[self defaults];
}
return self;
}
#pragma mark - Setter/Getter
- (void)setFrame:(CGRect)frame {
[super setFrame:frame];
angle = [self angleFromValue];
}
- (CGFloat)radius {
//radius = self.frame.size.height/2 - [self circleDiameter]/2;
return self.frame.size.height/2 - _lineWidth/2 - ([self circleDiameter]-_lineWidth) - _lineRadiusDisplacement;
}
- (void)setCurrentValue:(float)currentValue {
_currentValue=currentValue;
if(_currentValue>_maximumValue) _currentValue=_maximumValue;
else if(_currentValue<_minimumValue) _currentValue=_minimumValue;
angle = [self angleFromValue];
[self setNeedsLayout];
[self setNeedsDisplay];
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
#pragma mark - drawing methods
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef ctx = UIGraphicsGetCurrentContext();
//Draw the unfilled circle
//CGContextAddArc(ctx, self.frame.size.width/2, self.frame.size.height/2, self.radius, 0, M_PI *2, 0);
CGContextAddArc(ctx, self.frame.size.width/2, self.frame.size.height/2, self.radius, 0, M_PI *2, 0);
[_unfilledColor setStroke];
CGContextSetLineWidth(ctx, _lineWidth);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
//Draw the filled circle
if((_handleType == EFDoubleCircleWithClosedCenter || _handleType == EFDoubleCircleWithOpenCenter) && fixedAngle > 5) {
CGContextAddArc(ctx, self.frame.size.width/2 , self.frame.size.height/2, self.radius, 3*M_PI/2, 3*M_PI/2-ToRad(angle+3), 0);
} else {
CGContextAddArc(ctx, self.frame.size.width/2 , self.frame.size.height/2, self.radius, 3*M_PI/2, 3*M_PI/2-ToRad(angle), 0);
}
[_filledColor setStroke];
CGContextSetLineWidth(ctx, _lineWidth);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
UIView *colourView = [[UIView alloc] initWithFrame:rect];
colourView.opaque = NO;
colourView.alpha = .7f;
//colourView.backgroundColor = [UIColor colorWithRed:0.13f green:0.14f blue:0.15f alpha:1.00f];
//CGContextSetStrokeColorWithColor(ctx, [UIColor greenColor].CGColor);
CGContextSetFillColorWithColor(ctx, [UIColor greenColor].CGColor );
// CGContextFillRect(ctx, (CGRect){ {0,0}, colourView.size} );
//Add the labels (if necessary)
if(labelsEvenSpacing != nil) {
[self drawLabels:ctx];
}
//The draggable part
[self drawHandle:ctx];
}
-(void) drawHandle:(CGContextRef)ctx{
CGContextSaveGState(ctx);
CGPoint handleCenter = [self pointFromAngle: angle];
if(_handleType == EFSemiTransparentWhiteCircle) {
[[UIColor colorWithWhite:0.3 alpha:0.7] set];
CGContextFillEllipseInRect(ctx, CGRectMake(handleCenter.x, handleCenter.y, _lineWidth, _lineWidth));
} else if(_handleType == EFSemiTransparentBlackCircle) {
[[UIColor colorWithWhite:0.0 alpha:0.7] set];
CGContextFillEllipseInRect(ctx, CGRectMake(handleCenter.x, handleCenter.y, _lineWidth, _lineWidth));
} else if(_handleType == EFDoubleCircleWithClosedCenter) {
[_handleColor set];
CGContextAddArc(ctx, handleCenter.x + (_lineWidth)/2, handleCenter.y + (_lineWidth)/2, _lineWidth, 0, M_PI *2, 0);
CGContextSetLineWidth(ctx, 7);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
CGContextFillEllipseInRect(ctx, CGRectMake(handleCenter.x, handleCenter.y, _lineWidth-1, _lineWidth-1));
} else if(_handleType == EFDoubleCircleWithOpenCenter) {
[_handleColor set];
CGContextAddArc(ctx, handleCenter.x + (_lineWidth)/2, handleCenter.y + (_lineWidth)/2, _lineWidth/2 + 5, 0, M_PI *2, 0);
CGContextSetLineWidth(ctx, 4);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
CGContextAddArc(ctx, handleCenter.x + _lineWidth/2, handleCenter.y + _lineWidth/2, _lineWidth/2, 0, M_PI *2, 0);
CGContextSetLineWidth(ctx, 2);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
} else if(_handleType == EFBigCircle) {
[_handleColor set];
CGContextFillEllipseInRect(ctx, CGRectMake(handleCenter.x-2.5, handleCenter.y-2.5, _lineWidth+5, _lineWidth+5));
}
CGContextRestoreGState(ctx);
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
CGPoint p1 = [self centerPoint];
CGPoint p2 = point;
CGFloat xDist = (p2.x - p1.x);
CGFloat yDist = (p2.y - p1.y);
double distance = sqrt((xDist * xDist) + (yDist * yDist));
return distance < self.radius + 11;
}
-(void) drawLabels:(CGContextRef)ctx {
if(labelsEvenSpacing == nil || [labelsEvenSpacing count] == 0) {
return;
} else {
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0
NSDictionary *attributes = #{ NSFontAttributeName: _labelFont,
NSForegroundColorAttributeName: _labelColor
};
#endif
CGFloat fontSize = ceilf(_labelFont.pointSize);
NSInteger distanceToMove = -[self circleDiameter]/2 - fontSize/2 - _labelDisplacement;
for (int i=0; i<[labelsEvenSpacing count]; i++)
{
NSString *label = [labelsEvenSpacing objectAtIndex:[labelsEvenSpacing count] - i - 1];
CGFloat percentageAlongCircle = i/(float)[labelsEvenSpacing count];
CGFloat degreesForLabel = percentageAlongCircle * 360;
CGSize labelSize=CGSizeMake([self widthOfString:label withFont:_labelFont], [self heightOfString:label withFont:_labelFont]);
CGPoint closestPointOnCircleToLabel = [self pointFromAngle:degreesForLabel withObjectSize:labelSize];
CGRect labelLocation = CGRectMake(closestPointOnCircleToLabel.x, closestPointOnCircleToLabel.y, labelSize.width, labelSize.height);
CGPoint centerPoint = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
float radiansTowardsCenter = ToRad(AngleFromNorth(centerPoint, closestPointOnCircleToLabel, NO));
labelLocation.origin.x = (labelLocation.origin.x + distanceToMove * cos(radiansTowardsCenter));
labelLocation.origin.y = (labelLocation.origin.y + distanceToMove * sin(radiansTowardsCenter));
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0
[label drawInRect:labelLocation withAttributes:attributes];
#else
[_labelColor setFill];
[label drawInRect:labelLocation withFont:_labelFont];
#endif
}
}
}
#pragma mark - UIControl functions
-(BOOL) beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
[super beginTrackingWithTouch:touch withEvent:event];
return YES;
}
-(BOOL) continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
[super continueTrackingWithTouch:touch withEvent:event];
CGPoint lastPoint = [touch locationInView:self];
[self moveHandle:lastPoint];
[self sendActionsForControlEvents:UIControlEventValueChanged];
return YES;
}
-(void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
[super endTrackingWithTouch:touch withEvent:event];
if(_snapToLabels && labelsEvenSpacing != nil) {
CGFloat newAngle=0;
float minDist = 360;
for (int i=0; i<[labelsEvenSpacing count]; i++) {
CGFloat percentageAlongCircle = i/(float)[labelsEvenSpacing count];
CGFloat degreesForLabel = percentageAlongCircle * 360;
if(abs(fixedAngle - degreesForLabel) < minDist) {
newAngle=degreesForLabel ? 360 - degreesForLabel : 0;
minDist = abs(fixedAngle - degreesForLabel);
}
}
angle = newAngle;
_currentValue = [self valueFromAngle];
[self setNeedsDisplay];
}
}
-(void)moveHandle:(CGPoint)point {
CGPoint centerPoint;
centerPoint = [self centerPoint];
int currentAngle = floor(AngleFromNorth(centerPoint, point, NO));
angle = 360 - 90 - currentAngle;
_currentValue = [self valueFromAngle];
[self setNeedsDisplay];
}
- (CGPoint)centerPoint {
return CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
}
#pragma mark - helper functions
-(CGPoint)pointFromAngle:(int)angleInt{
//Define the Circle center
CGPoint centerPoint = CGPointMake(self.frame.size.width/2 - _lineWidth/2, self.frame.size.height/2 - _lineWidth/2);
//Define The point position on the circumference
CGPoint result;
result.y = round(centerPoint.y + self.radius * sin(ToRad(-angleInt-90))) ;
result.x = round(centerPoint.x + self.radius * cos(ToRad(-angleInt-90)));
return result;
}
-(CGPoint)pointFromAngle:(int)angleInt withObjectSize:(CGSize)size{
//Define the Circle center
CGPoint centerPoint = CGPointMake(self.frame.size.width/2 - size.width/2, self.frame.size.height/2 - size.height/2);
//Define The point position on the circumference
CGPoint result;
result.y = round(centerPoint.y + self.radius * sin(ToRad(-angleInt-90))) ;
result.x = round(centerPoint.x + self.radius * cos(ToRad(-angleInt-90)));
return result;
}
- (CGFloat)circleDiameter {
if(_handleType == EFSemiTransparentWhiteCircle) {
return _lineWidth;
} else if(_handleType == EFSemiTransparentBlackCircle) {
return _lineWidth;
} else if(_handleType == EFDoubleCircleWithClosedCenter) {
return _lineWidth * 2 + 3.5;
} else if(_handleType == EFDoubleCircleWithOpenCenter) {
return _lineWidth + 2.5 + 2;
} else if(_handleType == EFBigCircle) {
return _lineWidth + 2.5;
}
return 0;
}
static inline float AngleFromNorth(CGPoint p1, CGPoint p2, BOOL flipped) {
CGPoint v = CGPointMake(p2.x-p1.x,p2.y-p1.y);
float vmag = sqrt(SQR(v.x) + SQR(v.y)), result = 0;
v.x /= vmag;
v.y /= vmag;
double radians = atan2(v.y,v.x);
result = ToDeg(radians);
return (result >=0 ? result : result + 360.0);
}
-(float) valueFromAngle {
if(angle < 0) {
_currentValue = -angle;
} else {
_currentValue = 270 - angle + 90;
}
fixedAngle = _currentValue;
return (_currentValue*(_maximumValue - _minimumValue))/360.0f;
}
- (float)angleFromValue {
angle = 360 - (360.0f*_currentValue/_maximumValue);
if(angle==360) angle=0;
return angle;
}
- (CGFloat) widthOfString:(NSString *)string withFont:(UIFont*)font {
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil];
return [[[NSAttributedString alloc] initWithString:string attributes:attributes] size].width;
}
- (CGFloat) heightOfString:(NSString *)string withFont:(UIFont*)font {
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil];
return [[[NSAttributedString alloc] initWithString:string attributes:attributes] size].height;
}
#pragma mark - public methods
-(void)setInnerMarkingLabels:(NSArray*)labels{
labelsEvenSpacing = labels;
[self setNeedsDisplay];
}
#end
EFCircularSlider.h
#import <UIKit/UIKit.h>
#interface EFCircularSlider : UIControl
typedef NS_ENUM(NSInteger, EFHandleType) {
EFSemiTransparentWhiteCircle,
EFSemiTransparentBlackCircle,
EFDoubleCircleWithOpenCenter,
EFDoubleCircleWithClosedCenter,
EFBigCircle
};
#property (nonatomic) float minimumValue;
#property (nonatomic) float maximumValue;
#property (nonatomic) float currentValue;
#property (nonatomic) int lineWidth;
#property (nonatomic) int lineRadiusDisplacement;
#property (nonatomic, strong) UIColor* filledColor;
#property (nonatomic, strong) UIColor* unfilledColor;
#property (nonatomic, strong) UIColor* handleColor;
#property (nonatomic) EFHandleType handleType;
#property (nonatomic, strong) UIFont* labelFont;
#property (nonatomic, strong) UIColor* labelColor;
#property (nonatomic, assign) NSInteger labelDisplacement;
#property (nonatomic) BOOL snapToLabels;
-(void)setInnerMarkingLabels:(NSArray*)labels;
#end
You can only do 1 CGContextDrawPath with the implicit path, and use kCGPathFill to fill it.
Also, if you want the translucent background color to not interfere with the overlaying arc:
draw the background first
use an actual transparent color (alpha < 1.0)
Do this:
CGContextAddArc(ctx, self.frame.size.width/2,
self.frame.size.height/2,
self.radius, 0, M_PI *2, 0);
CGContextClosePath(ctx);
CGContextSetFillColorWithColor(ctx,
[UIColor colorWithRed:0
green:.5 // dark green
blue:0
alpha:.25] // translucent
.CGColor );
CGContextDrawPath(ctx, kCGPathFill);
...before you do that (redefine CGContextAddArc)
CGContextAddArc(ctx, self.frame.size.width/2,
self.frame.size.height/2,
self.radius, 0, M_PI *2, 0);
CGContextClosePath(ctx);
[_unfilledColor setStroke];
CGContextSetLineWidth(ctx, _lineWidth);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
PS. Nice control. Make sources public when anti-aliased and ready!
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];
}
}
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
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
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
Can Anyone help me? Give me some ideas to achieve this:)
make an UIView class declare in .h file
CGFloat startAngle;
CGFloat endAngle;
#property(assign) int percent;
Replace initWithFrame and drawRect method in .m class
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor whiteColor];
// Determine our start and stop angles for the arc (in radians)
startAngle = M_PI * 1;
endAngle = startAngle + (M_PI * 2); }
return self;
}
- (void)drawRect:(CGRect)rect
{
NSString* textContent = [NSString stringWithFormat:#"%d", percent];
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
// Create our arc, with the correct angles
[bezierPath addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:130
startAngle:startAngle
endAngle:(endAngle - startAngle) * (percent / 100.0) + startAngle
clockwise:YES];
// Set the display for the path, and stroke it
bezierPath.lineWidth = 20;
[[UIColor redColor] setStroke];
[bezierPath stroke];
// Text Drawing
CGRect textRect = CGRectMake((rect.size.width / 2.0) - 71/2.0, (rect.size.height / 2.0) - 45/2.0, 71, 45);
[[UIColor blackColor] setFill];
[textContent drawInRect: textRect withFont: [UIFont fontWithName: #"Helvetica-Bold" size: 42.5] lineBreakMode: NSLineBreakByWordWrapping alignment: NSTextAlignmentCenter];
}
In your Controller .h import CornerRadious and declare
NSTimer *m_timer;
CornerRadious *cr;
In .m class in ViewDidLoadMethod
cr = [[CornerRadious alloc] initWithFrame:self.view.bounds];
cr.percent = 50;
[self.view addSubview:cr];
aslo make add in these method
- (void)viewDidAppear:(BOOL)animated
{
// Kick off a timer to count it down
m_timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(decrementSpin) userInfo:nil repeats:YES];
}
- (void)decrementSpin
{
// If we can decrement our percentage, do so, and redraw the view
if (cr.percent > 0) {
cr.percent = cr.percent - 1;
[cr setNeedsDisplay];
}
else {
[m_timer invalidate];
m_timer = nil;
}
}
Hope it work
I have a UIView subclass called dpCurvedLabel. It uses CATextLayers to curved text around an arc. It works fine, except that I can't get it perfectly centered in the layers parent view. I want the center point for the arc to be at the very center of the view (even if the view is smaller) so all the text characters are the same distance from the center.
I can get it CLOSE but it's always a at least few pixels off. The amount it's 'off' seems to be effected by the frame size I give each CATextLayer. There's something wrong with the math, but I can't figure out what. My code:
// instantiate a dpCurvedLabel in a super view
dpCurvedLabel *curvedLabel = [[dpCurvedLabel alloc]
initWithFrame:CGRectMake(0, 0, 200, 200) arcSize:360 radius:50
text:#"curve curve curve " font:[UIFont fontWithName:#"HelveticaNeue" size:18]
textColor:[UIColor whiteColor]];
// You can animate a rotation to see a more pronounced effect
// [curvedLabel rotateContinously];
[self addSubview:curvedLabel];
dpCurvedLabel.h
#import <UIKit/UIKit.h>
#interface dpCurvedLabel : UIView
#property CGFloat arcSize;
#property CGFloat radius;
#property (strong) NSString *text;
#property (strong) UIFont *font;
#property (strong) UIColor *textColor;
- (id)initWithFrame:(CGRect)frame arcSize:(CGFloat)arcSize radius:(CGFloat)radius
text:(NSString *)text font:(UIFont *)font
textColor:(UIColor *)textColor;
- (void)rotateContinously;
+ (void)makeCurvedText:(CALayer *)layer arcSize:(CGFloat)arcSize radius:(CGFloat)radius
text:(NSString *)text font:(UIFont *)font textColor:(UIColor *)textColor;
#end
dpCurvedLabel.m
#import "dpCurvedLabel.h"
#implementation dpCurvedLabel
- (id)initWithFrame:(CGRect)frame arcSize:(CGFloat)arcSize radius:(CGFloat)radius text:(NSString *)text font:(UIFont *)font textColor:(UIColor *)textColor
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.opaque = NO;
self.clipsToBounds = NO;
self.arcSize = arcSize;
self.radius = radius;
self.text = text;
self.font = font;
self.textColor = textColor;
}
return self;
}
- (void)layoutSublayersOfLayer:(CALayer *)layer
{
[super layoutSublayersOfLayer:layer];
NSLog(#"laying out sublayers!");
[dpCurvedLabel makeCurvedText:layer arcSize:self.arcSize radius:self.radius text:self.text font:self.font textColor:self.textColor];
}
+ (void)makeCurvedText:(CALayer *)layer arcSize:(CGFloat)arcSize radius:(CGFloat)radius text:(NSString *)text font:(UIFont *)font textColor:(UIColor *)textColor
{
layer.sublayers = nil;
layer.masksToBounds = NO;
CGFloat arcStart = 0;
CGFloat shiftH = 0;
CGFloat shiftV = 0;
BOOL clockwise = YES;
BOOL debugMode = YES;
CGFloat xcenter = CGRectGetMidX(layer.bounds);
CGFloat ycenter = CGRectGetMidY(layer.bounds);
CGFloat angle = arcStart;
CGFloat angleStep = arcSize / text.length;
for ( NSUInteger i = 0; i < text.length; ++i )
{
NSRange range = { .location = i, .length = 1 };
NSString *c = [text substringWithRange:range];
CGFloat yoffset = sin( degreesToRadians(angle) ) * radius;
CGFloat xoffset = cos( degreesToRadians(angle) ) * radius;
CGFloat rotAngle = 90 - angle;
if ( clockwise )
{
yoffset = -yoffset;
rotAngle = -90 + angle;
}
CATextLayer* tl = [[CATextLayer alloc] init];
tl.masksToBounds = NO;
tl.wrapped = NO;
tl.truncationMode = kCATruncationNone;
if ( debugMode )
{
tl.borderWidth = 1;
tl.cornerRadius = 3;
tl.borderColor = [UIColor whiteColor].CGColor;
}
// Text layer frame determined here. Effects how arc is centered.
CGSize charSize = CGSizeMake(20, 20);
tl.frame = CGRectMake( shiftH + xcenter - xoffset, shiftV + ycenter + yoffset, charSize.width, charSize.height );
// *******
tl.font = (__bridge CFTypeRef)(font.fontName);
tl.fontSize = font.pointSize;
tl.foregroundColor = textColor.CGColor;
tl.string = c;
tl.alignmentMode = #"center";
tl.transform = CATransform3DMakeAffineTransform( CGAffineTransformMakeRotation( degreesToRadians(rotAngle) ) );
[layer addSublayer:tl];
angle += angleStep;
}
if ( debugMode )
{
layer.backgroundColor = RGBA(0x00, 0x00, 0x00, .6).CGColor;
}
}
- (void)rotateContinously
{
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 /* full rotation*/ * 1 * 1 ];
rotationAnimation.duration = 5;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = INT_MAX;
[self.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
#end
What's wrong with the math here? Why won't this text arc center itself?
The problem comes from the fact that you are setting the frame of each text layer instead of its position. This means that you are positioning the lower left corner to be at the location where the position should be. Do this instead:
tl.position = CGPointMake( shiftH + xcenter - xoffset, shiftV + ycenter + yoffset);
tl.bounds = CGRectMake(0,0,charSize.width,charSize.height);
And you will find your layers to be exactly where you want them.