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
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've a project where I've created a custom UIPopoverController class and used in several parts of my project but now from iOS 9 UIPopoverController is deprecated. I want to know if there is any easy way that I can modify my existing popoverclass so that the other parts where I've used it remain unchanged or has minimum changes. Below is the custom class that I've created.
**myCustomPopover.h file**
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface myCustomPopover : UIPopoverController
#property (readonly) UIColor *tintColor;
- (id)initWithContentViewController:(UIViewController *)viewController andTintColor: (UIColor *)tintColor;
#end
**mycustomPopover.m file**
#import "myCustomPopover.h"
#pragma mark - Internal Constants
CGFloat const contentInset = 5.0;
CGFloat const capInset = 25.0;
CGFloat const arrowHeight = 25.0;
CGFloat const arrowBase = 25.0;
#interface myCustomPopoverControllerBackgroundView : UIPopoverBackgroundView
{
UIImageView *borderImageView;
UIImageView *arrowImageView;
}
+ (UIColor *)currentTintColor;
+ (void)setCurrentTintColor: (UIColor *)tintColor;
#end
#implementation myCustomPopoverControllerBackgroundView
#pragma mark - Internal Class Variables
static UIColor *currentTintColor;
#pragma mark - Initializers
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame: frame];
if (!self)
return nil;
UIGraphicsBeginImageContext(CGSizeMake(60, 60));
UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, 0, 60, 60)
cornerRadius: 8];
[currentTintColor setFill];
[borderPath fill];
UIImage *borderImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIEdgeInsets capInsets = UIEdgeInsetsMake(capInset, capInset, capInset, capInset);
borderImageView = [[UIImageView alloc] initWithImage: [borderImage resizableImageWithCapInsets: capInsets]];
UIGraphicsBeginImageContext(CGSizeMake(25, 25));
UIBezierPath *arrowPath = [UIBezierPath bezierPath];
[arrowPath moveToPoint: CGPointMake(12.5, 0)];
[arrowPath addLineToPoint: CGPointMake(25, 25)];
[arrowPath addLineToPoint: CGPointMake(0, 25)];
[arrowPath addLineToPoint: CGPointMake(12.5, 0)];
UIGraphicsBeginImageContext(CGSizeMake(24, 15));
self.opaque = NO;
[currentTintColor setFill];
[arrowPath fill];
UIImage *arrowImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
arrowImageView = [[UIImageView alloc] initWithImage: arrowImage];
arrowImageView.layer.shadowColor = [UIColor blackColor].CGColor;
arrowImageView.layer.shadowOpacity = .4;
arrowImageView.layer.shadowRadius = 2;
arrowImageView.layer.shadowOffset = CGSizeMake(0, 1);
arrowImageView.layer.masksToBounds = YES;
[self addSubview: borderImageView];
[self addSubview: arrowImageView];
return self;
}
#pragma mark - Class Accessors and Mutators
+ (UIColor *)currentTintColor
{
return currentTintColor;
}
+ (void)setCurrentTintColor:(UIColor *)tintColor
{
currentTintColor = tintColor;
}
#pragma mark - Class Handlers
+ (UIEdgeInsets)contentViewInsets
{
return UIEdgeInsetsMake(contentInset, contentInset, contentInset, contentInset);
}
+ (CGFloat)arrowHeight
{
return arrowHeight;
}
+ (CGFloat)arrowBase
{
return arrowBase;
}
-(void) setArrowOffset:(CGFloat)_arrowOffset
{
arrowOffset = _arrowOffset;
[self setNeedsLayout];
}
-(void) setArrowDirection:(UIPopoverArrowDirection)_arrowDirection
{
arrowDirection = _arrowDirection;
[self setNeedsLayout];
}
#pragma mark - View Handlers
#synthesize arrowOffset;
#synthesize arrowDirection;
-(void)layoutSubviews
{
[super layoutSubviews];
CGFloat popoverImageOriginX = 0;
CGFloat popoverImageOriginY = 0;
CGFloat popoverImageWidth = self.bounds.size.width;
CGFloat popoverImageHeight = self.bounds.size.height;
CGFloat arrowImageOriginX = 0;
CGFloat arrowImageOriginY = 0;
CGFloat arrowImageWidth = arrowBase;
CGFloat arrowImageHeight = arrowHeight ;
CGAffineTransform rotation = CGAffineTransformIdentity;
CGFloat factor=0.0;
// Radius value you used to make rounded corners in your popover background image
CGFloat cornerRadius = 8;
switch (self.arrowDirection) {
case UIPopoverArrowDirectionUp:
popoverImageOriginY = arrowHeight - factor;
popoverImageHeight = self.bounds.size.height - arrowHeight;
// Calculating arrow x position using arrow offset, arrow width and popover width
arrowImageOriginX = roundf((self.bounds.size.width - arrowBase) / 2 + self.arrowOffset);
// If arrow image exceeds rounded corner arrow image x postion is adjusted
if (arrowImageOriginX + arrowBase > self.bounds.size.width - cornerRadius)
{
arrowImageOriginX -= cornerRadius;
}
if (arrowImageOriginX < cornerRadius)
{
arrowImageOriginX += cornerRadius;
}
break;
case UIPopoverArrowDirectionDown:
popoverImageHeight = self.bounds.size.height - arrowHeight + factor;
arrowImageOriginX = roundf((self.bounds.size.width - arrowBase) / 2 + self.arrowOffset);
if (arrowImageOriginX + arrowBase > self.bounds.size.width - cornerRadius)
{
arrowImageOriginX -= cornerRadius;
}
if (arrowImageOriginX < cornerRadius)
{
arrowImageOriginX += cornerRadius;
}
arrowImageOriginY = popoverImageHeight - factor;
rotation = CGAffineTransformMakeRotation(M_PI);
break;
case UIPopoverArrowDirectionLeft:
popoverImageOriginX = arrowHeight - factor;
popoverImageWidth = self.bounds.size.width - arrowHeight;
arrowImageOriginY = roundf((self.bounds.size.height - arrowBase) / 2 + self.arrowOffset);
if (arrowImageOriginY + arrowBase > self.bounds.size.height - cornerRadius)
{
arrowImageOriginY -= cornerRadius;
}
if (arrowImageOriginY < cornerRadius)
{
arrowImageOriginY += cornerRadius;
}
arrowImageWidth = arrowHeight;
arrowImageHeight = arrowBase;
rotation = CGAffineTransformMakeRotation(-M_PI_2);
break;
case UIPopoverArrowDirectionRight:
popoverImageWidth = self.bounds.size.width - arrowHeight + factor;
arrowImageOriginX = popoverImageWidth - factor;
arrowImageOriginY = roundf((self.bounds.size.height - arrowBase) / 2 + self.arrowOffset);
if (arrowImageOriginY + arrowBase > self.bounds.size.height - cornerRadius)
{
arrowImageOriginY -= cornerRadius;
}
if (arrowImageOriginY < cornerRadius)
{
arrowImageOriginY += cornerRadius;
}
arrowImageWidth = arrowHeight;
arrowImageHeight = arrowBase;
rotation = CGAffineTransformMakeRotation(M_PI_2);
break;
default:
// For popovers without arrows
popoverImageHeight = self.bounds.size.height - arrowHeight + factor;
break;
}
borderImageView.frame = CGRectMake(popoverImageOriginX, popoverImageOriginY, popoverImageWidth, popoverImageHeight);
arrowImageView.frame = CGRectMake(arrowImageOriginX, arrowImageOriginY, arrowImageWidth, arrowImageHeight);
[arrowImageView setTransform: rotation];
}
#end
#implementation myCustomPopoverController
#pragma mark - Properties
#synthesize tintColor;
#pragma mark - Initializers
- (id)initWithContentViewController:(UIViewController *)viewController
{
self = [self initWithContentViewController: viewController
andTintColor: [UIColor blackColor]];
return self;
}
- (id)initWithContentViewController:(UIViewController *)viewController andTintColor:(UIColor *)aTintColor
{
self = [super initWithContentViewController: viewController];
if (!self)
return nil;
[super setPopoverBackgroundViewClass: [myCustomPopoverControllerBackgroundView class]];
currentTintColor = aTintColor;
tintColor = aTintColor;
return self;
}
#pragma mark - Overriders
- (void)setPopoverBackgroundViewClass:(Class)popoverBackgroundViewClass {}
#end
I've tried to change the subclass to UIPopoverPresentationController but has some errors like no interface for initWithContentViewController. Is this the right way to approach?
You have to use the modalPresentationStyle of view controller to UIModalPresentationPopover.
no need, to worry. we have fppopover
that need needs uiviewcontroller, then show that like popover.
No need to custom youself.
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!
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 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.