I need some advices about drawing in iOS and more specifically about performance drawing. I read a lot of articles about the drawing in iOS7 but I didn't succeed to obtain the correct result.
I have dots than I need to link them in the correct order.
Thanks to my Dot & Elastic classes, I succeed to have this render. I'm very satisfied :
http://d.pr/v/nYzH
This dots represents a card and I need to use a timeline to navigate between each card.
I use iCarousel library for realize this objective. I work like a UITableView except we manage view. Views can be reused etc...
But the problem start there. This is the result :
http://d.pr/v/y7dq
First problem : dots have low resolution.
Second real problem : I got some lags..
You can follow here my files used for draw dot & elastic.
Dot.m file
#interface Dot () {
CAShapeLayer *_foreground;
CAShapeLayer *_background;
UIBezierPath *_path;
}
#end
#implementation Dot
- (id)initWithPoint:(CGPoint)point andRadius:(CGFloat)radius
{
self = [super init];
if (self) {
self.frame = CGRectMake(point.x, point.y, (radius + 10) * 2, (radius + 10) * 2);
self.radius = radius;
self.color = [UIColor colorWithHexString:#"#FFFFFF"];
[self setPosition:point];
[self setupPath];
_background = [CAShapeLayer layer];
[_background setFrame:CGRectMake(0, 0, self.width, self.height)];
[_background setPath:_path.CGPath];
[self.layer addSublayer:_background];
[self drawStrokedDotToLayer:_background];
_foreground = [CAShapeLayer layer];
[_foreground setFrame:CGRectMake(0, 0, self.width, self.height)];
[_foreground setPath:_path.CGPath];
[self.layer addSublayer:_foreground];
[self drawPlainDotToLayer:_foreground];
[self setBackgroundColor:[UIColor clearColor]];
self.state = DotStateHidden;
}
return self;
}
- (void)setupPath
{
_path = [UIBezierPath bezierPath];
[_path addArcWithCenter:CGPointMake(self.width*.5f, self.height*.5f) radius:self.radius startAngle:0 endAngle:M_PI * 2 clockwise:NO];
}
#pragma mark - Setters
- (void)setPosition:(CGPoint)position
{
self.x = position.x - self.width*.5f;
self.y = position.y - self.width*.5f;
}
- (CGPoint)position
{
return CGPointMake(self.x + self.width*.5f, self.y + self.height*.5f);
}
- (void)setState:(DotState)state
{
_state = state;
CAKeyframeAnimation *foregroundAnim = nil;
CAKeyframeAnimation *backgroundAnim = nil;
switch (_state) {
case DotStateFeedback:
{
self.color = [UIColor colorWithHexString:#"#FFFFFF"];
[self drawFeedback:_foreground];
[self removeGlow:_foreground];
foregroundAnim = [self animation:ScaleIn function:ExponentialEaseOut duration:1.f];
break;
}
case DotStateVisible:
{
self.color = [UIColor colorWithHexString:#"#FFFFFF"];
[self drawPlainDotToLayer:_foreground];
[self drawGlow:_foreground];
[self drawStrokedDotToLayer:_background];
foregroundAnim = [self animation:ScaleIn function:ExponentialEaseOut duration:.2f];
break;
}
case DotStateNext:
{
self.color = [UIColor colorWithHexString:#"#FFFFFF"];
[self drawStrokedDotToLayer:_background];
foregroundAnim = [self animation:ScaleOut function:ExponentialEaseOut duration:.16f];
backgroundAnim = [self animation:ScaleIn function:ExponentialEaseOut duration:.25f];
[foregroundAnim setBeginTime:CACurrentMediaTime() + .04f];
break;
}
case DotStateHidden:
default:
{
self.color = [UIColor colorWithHexString:#"#333333"];
[self drawPlainDotToLayer:_foreground];
[self removeGlow:_foreground];
foregroundAnim = [self animation:ScaleIn function:ExponentialEaseOut duration:.5f];
backgroundAnim = [self animation:ScaleOut function:ExponentialEaseOut duration:.25f];
break;
}
}
if (foregroundAnim) [_foreground addAnimation:foregroundAnim forKey:nil];
if (backgroundAnim) [_background addAnimation:backgroundAnim forKey:nil];
}
#pragma mark - Drawing
- (void)drawStrokedDotToLayer:(CAShapeLayer *)layer
{
[layer setLineWidth:2.f];
[layer setStrokeColor:self.color.CGColor];
[layer setFillColor:[UIColor colorWithHexString:#"#1F1F1F"].CGColor];
}
- (void)drawPlainDotToLayer:(CAShapeLayer *)layer
{
[layer setLineWidth:2.f];
[layer setStrokeColor:[UIColor clearColor].CGColor];
[layer setFillColor:self.color.CGColor];
}
- (void)drawFeedback:(CAShapeLayer *)layer
{
[layer setLineWidth:2.f];
[layer setStrokeColor:[UIColor clearColor].CGColor];
[layer setFillColor:[UIColor colorWithHex:0xFFFFFF andAlpha:.025f].CGColor];
}
- (void)drawGlow:(CAShapeLayer *)layer
{
[layer setShadowRadius:8];
[layer setShadowOpacity:1.f];
[layer setShadowOffset:CGSizeMake(0, 0)];
[layer setShadowColor:[UIColor whiteColor].CGColor];
[layer didChangeValueForKey:#"path"];
}
- (void)removeGlow:(CAShapeLayer *)layer
{
[layer setShadowRadius:0];
[layer setShadowOpacity:0.f];
[layer setShadowOffset:CGSizeMake(0, 0)];
[layer setShadowColor:[UIColor clearColor].CGColor];
[layer didChangeValueForKey:#"path"];
}
- (CAKeyframeAnimation *)animation:(NSInteger)name function:(AHEasingFunction)function duration:(CGFloat)duration
{
CAKeyframeAnimation *animation;
switch (name) {
case ScaleIn:
{
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.scale" function:function fromValue:0.f toValue:1.f];
break;
}
case ScaleInFeedback:
{
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.scale" function:function fromValue:0.f toValue:3.f];
break;
}
case ScaleOut:
{
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.scale" function:function fromValue:1.f toValue:0.f];
break;
}
default:
{
animation = [CAKeyframeAnimation animationWithKeyPath:#"opacity" function:function fromValue:0.f toValue:1.f];
break;
}
}
animation.duration = duration;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
return animation;
}
#end
Elastic.m
#interface Elastic () {
UIBezierPath *_path;
UIImage *_image;
}
#end
#implementation Elastic
- (id)initWithDotOne:(Dot *)aDotOne DotTwo:(Dot *)aDotTwo
{
self = [super initWithFrame:[UIScreen mainScreen].bounds];
if (self) {
self.dotOne = aDotOne;
self.dotTwo = aDotTwo;
self.displayDots = NO;
self.feedBack = NO;
self.curveRadius = 4;
[self setBackgroundColor:[UIColor clearColor]];
_path = [UIBezierPath bezierPath];
[self updatePath];
}
return self;
}
- (void)setDisplayDots:(BOOL)displayDots
{
_displayDots = displayDots;
if (_displayDots) {
[self addSubview:self.dotTwo];
} else {
[self.dotTwo removeFromSuperview];
}
}
- (void)drawRect:(CGRect)rect
{
[_image drawInRect:rect];
}
- (void)updatePath
{
// Initialize variables
CGFloat dist = distance(self.dotOne.position, self.dotTwo.position);
CGFloat angle = angleBetweenPoints(self.dotOne.position, self.dotTwo.position) + M_PI * 1.5;
// Points
CGPoint ptA = CGPointMake(
self.dotOne.position.x + cosf(angle) * (self.dotOne.radius - self.curveRadius),
self.dotOne.position.y + sinf(angle) * (self.dotOne.radius - self.curveRadius)
);
CGPoint ptB = CGPointMake(
self.dotOne.position.x + cosf(angle + M_PI) * (self.dotOne.radius - self.curveRadius),
self.dotOne.position.y + sinf(angle + M_PI) * (self.dotOne.radius - self.curveRadius)
);
CGPoint ptC = CGPointMake(
self.dotTwo.position.x + cosf(angle) * (self.dotTwo.radius - self.curveRadius),
self.dotTwo.position.y + sinf(angle) * (self.dotTwo.radius - self.curveRadius)
);
CGPoint ptD = CGPointMake(
self.dotTwo.position.x + cosf(angle + M_PI) * (self.dotTwo.radius - self.curveRadius),
self.dotTwo.position.y + sinf(angle + M_PI) * (self.dotTwo.radius - self.curveRadius)
);
// Bezier
CGFloat mapA = angle + M_PI_2 + map(dist, 150, 350, 0.0001, 0.0005);
CGFloat mapB = angle + M_PI_2 - map(dist, 150, 350, 0.0001, 0.0005);
CGPoint bzA = CGPointMake(self.dotOne.position.x + cosf(mapA) * dist*.5f, self.dotOne.position.y + sinf(mapA) * dist*.5f);
CGPoint bzB = CGPointMake(self.dotOne.position.x + cosf(mapB) * dist*.5f, self.dotOne.position.y + sinf(mapB) * dist*.5f);
// Start drawing path
[_path moveToPoint:ptA];
[_path addQuadCurveToPoint:ptC controlPoint:bzA];
[_path addLineToPoint:ptD];
[_path addQuadCurveToPoint:ptB controlPoint:bzB];
[self drawBitmap];
[self setNeedsDisplay];
}
- (void)drawBitmap
{
_image = nil;
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
[_image drawAtPoint:CGPointZero];
[[UIColor whiteColor] setFill];
[_path fill];
_image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[_path removeAllPoints];
}
#end
Your videos look as if they were recorded on the simulator, not the device. Simulator testing is meaningless for performance, particularly graphics performance, since a dedicated GPU is not available to the simulator.
GPU-heavy operations will often be considerably worse on the simulator than the device for this reason.
So, I can offer you the following advice :
Run performance testing on a device, ideally the slowest one you are targeting.
Use the core animation instrument which will give you frame rates and time profiling, and will highlight the expensive areas of your code.
Investigate the various graphical debugging options available via instruments on the device as well - offscreen rendering, redrawing and so forth
If you like iCarousel, then the author of that project, Nick Lockwood, has an excellent book on core animation for iOS, which contains a chapter about performance tuning.
Related
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."
I have code to draw the rectangle as
- (void)drawRect:(CGRect)frame {
UIBezierPath* rectanglePath = [UIBezierPath
bezierPathWithRect:CGRectMake(67, 50, 2, _height)];
[UIColor.grayColor setFill];
[rectanglePath fill];
}
where the _height value will change continuously on click
i have a code like this
- (void)initialize {
self.userInteractionEnabled = YES;
_displayLink = [CADisplayLink displayLinkWithTarget:self
selector:#selector(redrawView:)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop]forMode:NSDefaultRunLoopMode];
NSMutableArray *array = [[NSMutableArray alloc]init];
[array addObjectsFromArray:#[#“10”,#“20”,#“30”,#“40”]];
}
- (void)dealloc {
[_displayLink invalidate];
[_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
_displayLink = nil;
}
- (void)setInteractionState:(InteractionState)interactionState {
if (_interactionState == Idle && interactionState != Idle) {
_displayLink.paused = NO;
}
_interactionState = interactionState;
}
- (void)redrawView:(CADisplayLink *)displayLink {
if (_interactionState == start) {
_height = [array firstObject];
[array removeObjectAtIndex:0];
[array addObject:_height];
}
}
how to change the height whenever the interaction state is playing?
Try to do this through Core Animation:
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(67, 50, 2, _height) cornerRadius:0.0];
shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
shapeLayer.lineWidth = 3.0;
shapeLayer.fillColor = [[UIColor clearColor] CGColor];
[self.view.layer addSublayer:shapeLayer];
UIBezierPath *newPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(67, 50, 2, _height) cornerRadius:0.0];
CABasicAnimation* pathAnim = [CABasicAnimation animationWithKeyPath:#"path"];
pathAnim.fromValue = (id)self.shapeLayer.path;
pathAnim.toValue = (id)newPath.CGPath;
pathAnim.duration = 2.0f;
[self.circle addAnimation:pathAnim forKey:#"redraw"];
self.shapeLayer.path = newPath.CGPath;
There are two methods in UIView class to redraw the view
- (void)setNeedsDisplay; to redraw the entire view
- (void)setNeedsDisplayInRect:(CGRect)rect; to redraw the given area
For example
- (void)changeHeightTo:(CGFloat)height {
_height = height;
[self setNeedsDisplay]; //this method will notify the system
//to call `drawRect` in next drawing cycle
}
Taking a next step from this post Drag UIView around Shape Comprised of CGMutablePaths , I am trying to add a line which is on one end pinned to the center of the pathLayer_ and the other end gets dragged along with the circular object (circleLayer in handleView_) on the path (figure-8).
The line has its own layer andview and path initiated in (void)initHandleView.
First: I am not able to get the line to go through the center of the pathLayer_:
- (void)viewDidLoad {
[super viewDidLoad];
[self initPathLayer];
[self initHandleView];
[self initHandlePanGestureRecognizer];
}
- (void)initPathLayer {
pathLayer_ = [CAShapeLayer layer];
pathLayer_.lineWidth = 1;
pathLayer_.fillColor = nil;
pathLayer_.strokeColor = [UIColor lightGrayColor].CGColor;
pathLayer_.lineCap = kCALineCapButt;
pathLayer_.lineJoin = kCALineJoinRound;
pathLayer_.frame = self.view.bounds;
pathLayerCenter_ = CGPointMake(CGRectGetMidX(pathLayer_.frame), CGRectGetMidY(pathLayer_.frame));
[self.view.layer addSublayer:pathLayer_];
}
- (void)initHandleView {
handlePathPointIndex_ = 0;
CGRect rect = CGRectMake(0, 0, 30, 30);
CAShapeLayer *circleLayer = [CAShapeLayer layer];
circleLayer.fillColor = [UIColor redColor].CGColor;
circleLayer.strokeColor = [UIColor redColor].CGColor;
circleLayer.lineWidth = 2;
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(rect, circleLayer.lineWidth, circleLayer.lineWidth)];
circleLayer.frame = rect;
circleLayer.path = circlePath.CGPath;
handleView_ = [[UIView alloc] initWithFrame:rect];
CGRect lineRect = CGRectMake(0,0,CGRectGetMaxX([[UIScreen mainScreen] bounds]), CGRectGetMaxY([[UIScreen mainScreen] bounds]));
CGPoint center = CGPointMake(CGRectGetMidX(pathLayer_.frame), CGRectGetMidY(pathLayer_.frame));
lineView_ = [[UIView alloc] initWithFrame:lineRect];
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.fillColor = [UIColor blueColor].CGColor;
lineLayer.strokeColor = [UIColor blueColor].CGColor;
lineLayer.lineWidth = 2;
lineLayer.frame = self.view.bounds;
UIBezierPath *linePath = [UIBezierPath bezierPath];
CGPoint circlePoint = CGPointMake(handleView_.center.x, handleView_.center.y);
[linePath moveToPoint:center]; //Center
[linePath addLineToPoint:circlePoint];
[linePath moveToPoint:circlePoint]; //Handle
lineLayer.path = linePath.CGPath;
[handleView_.layer insertSublayer:circleLayer above:handleView_.layer];
[handleView_.layer insertSublayer:lineLayer above:handleView_.layer];
// handleView_.layer.masksToBounds = YES;
float direction = DEGREES_TO_RADIANS(10);
CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(direction);
[handleView_ setTransform:rotationTransform];
[self.view addSubview:lineView_];
[self.view addSubview:handleView_];
}
And second: I am not sure I do the right thing with the PanGesturerecognizer:
- (void)handleWasPanned:(UIPanGestureRecognizer *)recognizer {
switch (recognizer.state) {
case UIGestureRecognizerStateBegan: {
desiredHandleCenter_ = handleView_.center;
break;
}
case UIGestureRecognizerStateChanged:
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
CGPoint translation = [recognizer translationInView:self.view];
desiredHandleCenter_.x += translation.x;
desiredHandleCenter_.y += translation.y;
[self moveHandleTowardPointAndRotateLine:desiredHandleCenter_];
break;
}
default:
break;
}
[recognizer setTranslation:CGPointZero inView:self.view];
}
- (void)moveHandleTowardPointAndRotateLine:(CGPoint)point {
CGFloat earlierDistance = [self distanceToPoint:point ifHandleMovesByOffset:-1];
CGFloat currentDistance = [self distanceToPoint:point ifHandleMovesByOffset:0];
CGFloat laterDistance = [self distanceToPoint:point ifHandleMovesByOffset:1];
if (currentDistance <= earlierDistance && currentDistance <= laterDistance)
return;
NSInteger step;
CGFloat distance;
if (earlierDistance < laterDistance) {
step = -1;
distance = earlierDistance;
} else {
step = 1;
distance = laterDistance;
}
NSInteger offset = step;
while (true) {
NSInteger nextOffset = offset + step;
CGFloat nextDistance = [self distanceToPoint:point ifHandleMovesByOffset:nextOffset];
if (nextDistance >= distance)
break;
distance = nextDistance;
offset = nextOffset;
}
handlePathPointIndex_ += offset;
// Make one end of the line move with handle (point) while the other is pinned to center of pathLayer_ (ie. in figure8 its the cross point, in a circle it's center)
//CGFloat rot = [self getTouchAngle:point];
CGFloat rot = atan2f((point.x - pathLayerCenter_.x), -(point.y - pathLayerCenter_.y));
handleView_.layer.transform = CATransform3DMakeRotation(rot, 0., 0., 1.);
[self layoutHandleView];
}
I'm just going to ignore your code and tell you how I'd modify my original answer to add the line you want.
First, I'm going to use a dedicated shape layer for the line, so I'll add an instance variable to reference the line layer:
#implementation ViewController {
CAShapeLayer *lineLayer_; // NEW!!!
UIBezierPath *path_;
CAShapeLayer *pathLayer_;
etc.
I need to initialize the line layer when I load the view:
- (void)viewDidLoad {
[super viewDidLoad];
[self initPathLayer];
[self initHandleView];
[self initHandlePanGestureRecognizer];
[self initLineLayer]; // NEW!!!
}
- (void)initLineLayer {
lineLayer_ = [CAShapeLayer layer];
lineLayer_.lineWidth = 1;
lineLayer_.fillColor = nil;
lineLayer_.strokeColor = [UIColor redColor].CGColor;
lineLayer_.lineCap = kCALineCapRound;
lineLayer_.lineJoin = kCALineJoinRound;
[self.view.layer addSublayer:lineLayer_];
}
I need to lay out the line layer when I lay out my top-level view (when it's first shown and on rotations):
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self createPath];
[self createPathPoints];
[self layoutPathLayer];
[self layoutHandleView];
[self layoutLineLayer]; // NEW!!!
}
- (void)layoutLineLayer {
lineLayer_.frame = self.view.bounds;
CGRect bounds = self.view.bounds;
CGPoint start = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CGPoint end = pathPoints_[handlePathPointIndex_];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:start];
[path addLineToPoint:end];
lineLayer_.path = path.CGPath;
}
Finally, I need to lay out the line layer again (to update its path) whenever I update the handle:
- (void)moveHandleTowardPoint:(CGPoint)point {
// I have omitted most of this method body for brevity.
// Just copy it from the original answer.
...
...
handlePathPointIndex_ += offset;
[self layoutHandleView];
[self layoutLineLayer]; // NEW!!!
}
Result:
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