Two routines one in objective C, the other in Swift same code essentially different results - objective-c

iOS 10.2 Swift 3.0
Trying to convert this code in objective C to Swift 3.0 and don't know the core graphics library too well.
#interface SPGripViewBorderView : UIView
#end
#implementation SPGripViewBorderView
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Clear background to ensure the content view shows through.
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
// (1) Draw the bounding box.
CGContextSetLineWidth(context, 1.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextAddRect(context, CGRectInset(self.bounds, kSPUserResizableViewInteractiveBorderSize/2, kSPUserResizableViewInteractiveBorderSize/2));
CGContextStrokePath(context);
// (2) Calculate the bounding boxes for each of the anchor points.
CGRect upperLeft = CGRectMake(0.0, 0.0, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize);
CGRect upperRight = CGRectMake(self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize, 0.0, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize);
CGRect lowerRight = CGRectMake(self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize, self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize);
CGRect lowerLeft = CGRectMake(0.0, self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize);
CGRect upperMiddle = CGRectMake((self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize)/2, 0.0, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize);
CGRect lowerMiddle = CGRectMake((self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize)/2, self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize);
CGRect middleLeft = CGRectMake(0.0, (self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize)/2, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize);
CGRect middleRight = CGRectMake(self.bounds.size.width - kSPUserResizableViewInteractiveBorderSize, (self.bounds.size.height - kSPUserResizableViewInteractiveBorderSize)/2, kSPUserResizableViewInteractiveBorderSize, kSPUserResizableViewInteractiveBorderSize);
// (3) Create the gradient to paint the anchor points.
CGFloat colors [] = {
0.4, 0.8, 1.0, 1.0,
0.0, 0.0, 1.0, 1.0
};
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;
// (4) Set up the stroke for drawing the border of each of the anchor points.
CGContextSetLineWidth(context, 1);
CGContextSetShadow(context, CGSizeMake(0.5, 0.5), 1);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
// (5) Fill each anchor point using the gradient, then stroke the border.
CGRect allPoints[8] = { upperLeft, upperRight, lowerRight, lowerLeft, upperMiddle, lowerMiddle, middleLeft, middleRight };
for (NSInteger i = 0; i < 8; i++) {
CGRect currPoint = allPoints[i];
CGContextSaveGState(context);
CGContextAddEllipseInRect(context, currPoint);
CGContextClip(context);
CGPoint startPoint = CGPointMake(CGRectGetMidX(currPoint), CGRectGetMinY(currPoint));
CGPoint endPoint = CGPointMake(CGRectGetMidX(currPoint), CGRectGetMaxY(currPoint));
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGContextRestoreGState(context);
CGContextStrokeEllipseInRect(context, CGRectInset(currPoint, 1, 1));
}
CGGradientRelease(gradient), gradient = NULL;
CGContextRestoreGState(context);
}
#end
Which I re-coded as this ...
import UIKit
class GripViewBorderView: UIView {
let kUserResizableViewInteractiveBorderSize:CGFloat = 10.0
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func draw(_ rect:CGRect) {
let context = UIGraphicsGetCurrentContext();
context!.saveGState();
// (1) Draw the bounding box.
context!.setLineWidth(1.0);
context!.setStrokeColor(UIColor.blue.cgColor)
//CGContextAddRect(context!, CGRectInset(self.bounds, (kSPUserResizableViewInteractiveBorderSize / 2.0), (kSPUserResizableViewInteractiveBorderSize / 2.0)));
context!.addRect(self.bounds.insetBy(dx: (kUserResizableViewInteractiveBorderSize / 2.0), dy: (kUserResizableViewInteractiveBorderSize / 2.0)))
context!.strokePath();
// (2) Calculate the bounding boxes for each of the anchor points.
let upperLeft = CGRect(x: 0.0, y: 0.0, width: kUserResizableViewInteractiveBorderSize, height: kUserResizableViewInteractiveBorderSize)
let upperRight = CGRect(x: self.bounds.size.width - kUserResizableViewInteractiveBorderSize, y: 0.0, width: kUserResizableViewInteractiveBorderSize, height: kUserResizableViewInteractiveBorderSize);
let lowerRight = CGRect(x: self.bounds.size.width - kUserResizableViewInteractiveBorderSize, y: self.bounds.size.height - kUserResizableViewInteractiveBorderSize, width: kUserResizableViewInteractiveBorderSize, height: kUserResizableViewInteractiveBorderSize);
let lowerLeft = CGRect(x: 0.0, y: self.bounds.size.height - kUserResizableViewInteractiveBorderSize, width: kUserResizableViewInteractiveBorderSize, height: kUserResizableViewInteractiveBorderSize);
let upperMiddle = CGRect(x: (self.bounds.size.width - kUserResizableViewInteractiveBorderSize)/2, y: 0.0, width: kUserResizableViewInteractiveBorderSize, height: kUserResizableViewInteractiveBorderSize);
let lowerMiddle = CGRect(x: (self.bounds.size.width - kUserResizableViewInteractiveBorderSize)/2, y: self.bounds.size.height - kUserResizableViewInteractiveBorderSize, width: kUserResizableViewInteractiveBorderSize, height: kUserResizableViewInteractiveBorderSize);
let middleLeft = CGRect(x: 0.0, y: (self.bounds.size.height - kUserResizableViewInteractiveBorderSize)/2, width: kUserResizableViewInteractiveBorderSize, height: kUserResizableViewInteractiveBorderSize);
let middleRight = CGRect(x: self.bounds.size.width - kUserResizableViewInteractiveBorderSize, y: (self.bounds.size.height - kUserResizableViewInteractiveBorderSize)/2, width: kUserResizableViewInteractiveBorderSize, height: kUserResizableViewInteractiveBorderSize);
// (3) Create the gradient to paint the anchor points.
let baseSpace = CGColorSpaceCreateDeviceRGB()
//let gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2)
let colors = [UIColor.red.cgColor,
UIColor.yellow.cgColor]
let gradient = CGGradient(colorsSpace: baseSpace, colors: colors as CFArray, locations: nil)
// (4) Set up the stroke for drawing the border of each of the anchor points.
context!.setLineWidth(1)
context!.setShadow(offset: CGSize(width: 0.5, height: 0.5), blur: 1)
context!.setStrokeColor(UIColor.white.cgColor)
// (5) Fill each anchor point using the gradient, then stroke the border.
let allPoints:[CGRect] = [ upperLeft, upperRight, lowerRight, lowerLeft, upperMiddle, lowerMiddle, middleLeft, middleRight ];
for i in 0 ... 7 {
let currPoint = allPoints[i]
context!.saveGState()
context!.addEllipse(in: currPoint)
context!.clip()
let startPoint = CGPoint(x: currPoint.midX, y: currPoint.minY);
let endPoint = CGPoint(x: currPoint.midX, y: currPoint.maxY);
context!.drawLinearGradient(gradient!, start: startPoint, end: endPoint, options: .init(rawValue: 0))
context!.saveGState()
context!.strokeEllipse(in: currPoint.insetBy(dx: 1, dy: 1))
}
context!.restoreGState()
}
}
But I missed something cause the OOC is on the left and my new Swift on the right. For reasons I cannot figure I get only a single dot drawn in my version, the OOC version draws 8 dots [correctly].

You have written context!.saveGState() at the end of the for loop in place of CGContextRestoreGState(context). It should, of course, be context!.restoreGState(). Simple typo!
Also, please remove all of the semi-colons, and use guard let context = UIGraphicsGetCurrentContext() else { return } to get rid of all those forced unwrappings!

Related

CGContextSetFill with gradient

I currently have a function where I draw a QR code
+ (void)drawQRCode:(QRcode *)code context:(CGContextRef)ctx size:(CGFloat)size {}
Currently, I can set the color including the colorspace using the following:
CGContextSetFillColorWithColor(ctx, [theColor colorUsingColorSpaceName:NSCalibratedWhiteColorSpace].CGColor);
I am trying to implement the ability to create a gradient I've converted two NSColors to an NSGradient - there doesn't appear to be any way of getting a gradient as an NSColor so I'm wondering what would be the best way of setting the fill of the context with a gradient?
Thanks in advance for any suggestions!
CGContext has drawLinearGradient
https://developer.apple.com/documentation/coregraphics/cgcontext/1454782-drawlineargradient
and drawRadialGradient
https://developer.apple.com/documentation/coregraphics/cgcontext/1455923-drawradialgradient
To use them to fill a shape, put the shape's path into the context, then use the shape as a clipping path before drawing the gradient.
Here's a drawRect from a view that demonstrates the technique:
- (void) drawRect: (CGRect) rect {
CGColorRef myColors[3] = {
[[UIColor redColor] CGColor],
[[UIColor yellowColor] CGColor],
[[UIColor blueColor] CGColor] };
CGFloat locations[3] = { 0.0, 0.25, 1.0 };
CGRect circleRect = CGRectInset([self bounds], 20, 20);
CGFloat circleRadius = circleRect.size.width / 2.0;
CGContextRef cgContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(cgContext);
CGContextSetFillColorWithColor(cgContext, [[UIColor blackColor] CGColor]);
CGContextFillRect(cgContext, [self bounds]);
CGContextRestoreGState(cgContext);
CGContextSaveGState(cgContext);
CGContextAddEllipseInRect(cgContext, circleRect);
CGContextClip(cgContext);
CGContextTranslateCTM(cgContext, CGRectGetMidX(circleRect), CGRectGetMidY(circleRect));
CGContextRotateCTM(cgContext, M_PI / 3.0);
CFArrayRef colors = CFArrayCreate(nil, (const void**) myColors, 3, &kCFTypeArrayCallBacks);
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, colors, locations);
CFRelease(colors);
CFRelease(colorSpace);
CGContextDrawLinearGradient(cgContext, gradient, CGPointMake(-circleRadius, 0),
CGPointMake(circleRadius, 0), kCGGradientDrawsAfterEndLocation + kCGGradientDrawsBeforeStartLocation);
CFRelease(gradient);
CGContextRestoreGState(cgContext);
}
Here's an equivalent playground in Swift.
import UIKit
import PlaygroundSupport
class CircleGradientView : UIView {
override func draw(_ rect: CGRect) {
let colors = [
UIColor.red.cgColor,
UIColor.yellow.cgColor, // Yellow
UIColor.blue.cgColor // Blue
]
let colorSpace = CGColorSpace(name: CGColorSpace.sRGB)!
let locations : [CGFloat] = [0.0, 0.25, 1.0]
let gradient = CGGradient(colorsSpace: colorSpace,
colors: colors as CFArray,
locations: locations)
if let context = UIGraphicsGetCurrentContext() {
let circleRect = self.bounds.insetBy(dx: 20, dy: 20)
let circleRadius = circleRect.width / 2.0
context.saveGState()
context.addEllipse(in: circleRect)
context.clip()
context.translateBy(x: circleRect.midX, y: circleRect.midY)
context.rotate(by: .pi / 3.0)
context.drawLinearGradient(gradient!,
start: CGPoint(x: -circleRadius, y: 0),
end: CGPoint(x: circleRadius, y: 0),
options: [.drawsAfterEndLocation, .drawsBeforeStartLocation])
context.restoreGState()
}
}
}
let myView = CircleGradientView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
PlaygroundSupport.PlaygroundPage.current.liveView = myView

Core Graphics AddQuadCureToPoint from Center of View/Circle

What I am trying to accomplish is drawing curved lines from a center of a view. So far with the help of much googling and this site...I've accomplished the image below.
http://www.flickr.com/photos/57562417#N00/9100635690/
What I would like to do is have curved lines like the following image...
http://www.flickr.com/photos/57562417#N00/9100634674/
Any and all help would be appreciated. I've included the code thus far.
#import <UIKit/UIKit.h>
#interface ChartView : UIView
#property (nonatomic,assign) int angle;
#end
----------------------------------------------------------------------
#import "ChartView.h"
#import <QuartzCore/QuartzCore.h>
/** Helper Functions **/
#define degree_to_radians(deg) ( (M_PI * (deg)) / 180.0 )
#define radians_to_degrees(rad) ( (180.0 * (rad)) / M_PI )
#interface ChartView(){
int radius;
}
#end
#implementation ChartView
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self){
self.backgroundColor = [UIColor whiteColor];
[self.layer setBorderWidth:1.0];
[self.layer setBorderColor:[UIColor redColor].CGColor];
//Define the circle radius taking into account the safe area
radius = self.frame.size.width/2;
//Initialize the Angle at 0
self.angle = 360;
}
return self;
}
-(void)drawRect:(CGRect)rect{
//Circle center
int numberOfSections = 96; //24 major lines
CGFloat angleSize = 2 * M_PI/numberOfSections;
CGContextRef context = UIGraphicsGetCurrentContext();
//draw the main outside circle
CGRect circleRect = CGRectMake(0.0, 0.0, 720., 720.);
CGContextSetLineWidth(context, .5);
CGContextSetRGBStrokeColor(context, 0.0, 2.0, 4.0, 1.0);
CGContextStrokeEllipseInRect(context, circleRect);
CGPoint centerPoint = CGPointMake(circleRect.size.width/2 , circleRect.size.height/2);
//setup for drawing lines from center, straight lines, need curved lines
CGContextTranslateCTM(context, 0.0, 0.0);
for (int x = 0; x < numberOfSections; x++) {
CGContextSetLineWidth(context, .5);
CGContextSetRGBStrokeColor(context, 0.0, 2.0, 4.0, 1.0);
CGContextMoveToPoint(context, self.frame.size.width/2, self.frame.size.height/2);
CGContextAddLineToPoint(context, centerPoint.x + radius * cos(angleSize * x) ,centerPoint.y + radius * sin(angleSize * x));
CGContextStrokePath(context);
}
CGContextSetLineWidth(context, .5);
CGContextSetRGBStrokeColor(context, 0.0, 2.0, 4.0, 1.0);
CGContextSetRGBFillColor(context, 1., 1., 1., 1.0);
//Taken from Hypnotizer code
float maxRadius = 360.0;
CGContextSetLineWidth(context, 0.5f);
CGContextSetRGBStrokeColor(context, 0.0, 2.0, 4.0, 1.0);
//need to add 100 lines form a certain radius to a max radius
for (float currentRadius = maxRadius; currentRadius > 99; currentRadius -=2.6) {
CGContextAddArc(context, centerPoint.x, centerPoint.y, currentRadius, 0.0, M_PI * 2.0, YES);
CGContextStrokePath(context);
}
}
#end
Alright, I've got some code for you that I tried out and does the curved line effect you mentioned in that second picture:
- (void)drawRect:(CGRect)rect {
radius = self.frame.size.width / 2;
// Circle center
int numberOfSections = 96; // 24 major lines
CGFloat angleSize = 2 * M_PI/ numberOfSections;
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect circleRect = CGRectMake(0.0, 0.0, 720., 720.);
CGPoint centerPoint = CGPointMake(circleRect.size.width / 2, circleRect.size.height / 2);
/* Create mask to create 'hole' in the center */
CGContextAddArc(context, centerPoint.x, centerPoint.y, 720 / 2.0, 0.0, M_PI * 2.0, YES);
CGContextAddArc(context, centerPoint.x, centerPoint.y, 100, 0.0, M_PI * 2.0, NO);
CGContextClip(context);
// draw the main outside circle
CGContextSetLineWidth(context, .5);
CGContextSetRGBStrokeColor(context, 0.0, 2.0, 4.0, 1.0);
CGContextStrokeEllipseInRect(context, circleRect);
// setup for drawing lines from center, straight lines, need curved lines
CGContextTranslateCTM(context, 0.0, 0.0);
for (int x = 0; x < numberOfSections; x++) {
CGContextSetLineWidth(context, .5);
CGContextSetRGBStrokeColor(context, 0.0, 2.0, 4.0, 1.0);
double angle = angleSize * x;
CGPoint outsidePoint = CGPointMake(centerPoint.x + radius * cos(angle), centerPoint.y + radius * sin(angle));
CGPoint control1 = CGPointMake(centerPoint.x + radius/3 * cos(angle-0.5), centerPoint.y + radius/3 * sin(angle-0.5));
CGPoint control2 = CGPointMake(centerPoint.x + (2*radius/3) * cos(angle-0.5), centerPoint.y + (2*radius/3) * sin(angle-0.5));
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(circleRect.size.width / 2, circleRect.size.height / 2)];
[bezierPath addCurveToPoint:outsidePoint controlPoint1:control1 controlPoint2:control2];
CGContextAddPath(context, bezierPath.CGPath);
CGContextStrokePath(context);
}
CGContextSetLineWidth(context, .5);
CGContextSetRGBStrokeColor(context, 0.0, 2.0, 4.0, 1.0);
CGContextSetRGBFillColor(context, 1., 1., 1., 1.0);
// Taken from Hypnotizer code
float maxRadius = 360.0;
CGContextSetLineWidth(context, 0.5f);
CGContextSetRGBStrokeColor(context, 0.0, 2.0, 4.0, 1.0);
// need to add 100 lines form a certain radius to a max radius
for (float currentRadius = maxRadius; currentRadius > 99; currentRadius -= 2.6) {
CGContextAddArc(context, centerPoint.x, centerPoint.y, currentRadius, 0.0, M_PI * 2.0, YES);
CGContextStrokePath(context);
}
}

Invert colors of image with transparent background

I have an uiimage with transparent background. if I invert the colors with the follow method, the transparent background change to white and then to black.
I want that it doesnt invert transparent pixels? how can i do that?
- (UIImage *) getImageWithInvertedPixelsOfImage:(UIImage *)image {
UIGraphicsBeginImageContextWithOptions(image.size, NO, 2);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeCopy);
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeDifference);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(),[UIColor whiteColor].CGColor);
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, image.size.width, image.size.height));
UIImage * result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
this is how I solved.
It is the code that you can find in other answers, but with the masking of the current image with the same image, with rotated context (for transforming from CG* coords to UI* coords).
- (UIImage *)invertImage:(UIImage *)originalImage
{
UIGraphicsBeginImageContext(originalImage.size);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeCopy);
CGRect imageRect = CGRectMake(0, 0, originalImage.size.width, originalImage.size.height);
[originalImage drawInRect:imageRect];
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeDifference);
// translate/flip the graphics context (for transforming from CG* coords to UI* coords
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 0, originalImage.size.height);
CGContextScaleCTM(UIGraphicsGetCurrentContext(), 1.0, -1.0);
//mask the image
CGContextClipToMask(UIGraphicsGetCurrentContext(), imageRect, originalImage.CGImage);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(),[UIColor whiteColor].CGColor);
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, originalImage.size.width, originalImage.size.height));
UIImage *returnImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return returnImage;
}
I created a Pod from #maggix's answer:
pod 'UIImage+InvertedImage'
This is a Swift 4.0 version of #maggix answer...
func invertImage(with originalImage: UIImage) -> UIImage? {
var image: UIImage?
UIGraphicsBeginImageContext(originalImage.size)
if let context = UIGraphicsGetCurrentContext() {
context.setBlendMode(.copy)
let imageRect = CGRect(x: 0, y: 0, width: originalImage.size.width, height: originalImage.size.height)
originalImage.draw(in: imageRect)
context.setBlendMode(.difference)
// translate/flip the graphics context (for transforming from CG* coords to UI* coords
context.translateBy(x: 0, y: originalImage.size.height)
context.scaleBy(x: 1.0, y: -1.0)
//mask the image
context.clip(to: imageRect, mask: originalImage.cgImage!)
context.setFillColor(UIColor.white.cgColor)
context.fill(CGRect(x: 0, y:0, width: originalImage.size.width, height: originalImage.size.height))
image = UIGraphicsGetImageFromCurrentImageContext()
}
UIGraphicsEndImageContext()
return image
}

Circular Progress Bars in IOS

I want to create a circular progress bar like the following:
How can I do that using Objective-C and Cocoa?
How I started doing it was creating a UIView and editing the drawRect, but I am bit lost. Any help would be greatly appreciated.
Thanks!
The basic concept is to use the UIBezierPath class to your advantage. You are able to draw arcs, which achieve the effect you're after. I've only had half an hour or so to have a crack at this, but my attempt is below.
Very rudimentary, it simply uses a stroke on the path, but here we go. You can alter/modify this to your exact needs, but the logic to do the arc countdown will be very similar.
In the view class:
#interface TestView () {
CGFloat startAngle;
CGFloat endAngle;
}
#end
#implementation TestView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor whiteColor];
// Determine our start and stop angles for the arc (in radians)
startAngle = M_PI * 1.5;
endAngle = startAngle + (M_PI * 2);
}
return self;
}
- (void)drawRect:(CGRect)rect
{
// Display our percentage as a string
NSString* textContent = [NSString stringWithFormat:#"%d", self.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];
}
For the view controller:
#interface ViewController () {
TestView* m_testView;
NSTimer* m_timer;
}
#end
- (void)viewDidLoad
{
// Init our view
[super viewDidLoad];
m_testView = [[TestView alloc] initWithFrame:self.view.bounds];
m_testView.percent = 100;
[self.view addSubview:m_testView];
}
- (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 (m_testView.percent > 0) {
m_testView.percent = m_testView.percent - 1;
[m_testView setNeedsDisplay];
}
else {
[m_timer invalidate];
m_timer = nil;
}
}
My example with magic numbers (for better understanding):
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(29, 29) radius:27 startAngle:-M_PI_2 endAngle:2 * M_PI - M_PI_2 clockwise:YES].CGPath;
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor greenColor].CGColor;
circle.lineWidth = 4;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration = 10;
animation.removedOnCompletion = NO;
animation.fromValue = #(0);
animation.toValue = #(1);
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[circle addAnimation:animation forKey:#"drawCircleAnimation"];
[imageCircle.layer.sublayers makeObjectsPerformSelector:#selector(removeFromSuperlayer)];
[imageCircle.layer addSublayer:circle];
I have implemented a simple library for iOS doing just that. It's based on the UILabel class so you can display whatever you want inside your progress bar, but you can also leave it empty.
Once initialized, you only have one line of code to set the progress :
[_myProgressLabel setProgress:(50/100))];
The library is named KAProgressLabel
You can check out my lib MBCircularProgressBar
For Swift use this,
let circle = UIView(frame: CGRectMake(0,0, 100, 100))
circle.layoutIfNeeded()
let centerPoint = CGPoint (x: circle.bounds.width / 2, y: circle.bounds.width / 2)
let circleRadius : CGFloat = circle.bounds.width / 2 * 0.83
var circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.5 * M_PI), endAngle: CGFloat(1.5 * M_PI), clockwise: true )
let progressCircle = CAShapeLayer()
progressCircle.path = circlePath.CGPath
progressCircle.strokeColor = UIColor.greenColor().CGColor
progressCircle.fillColor = UIColor.clearColor().CGColor
progressCircle.lineWidth = 1.5
progressCircle.strokeStart = 0
progressCircle.strokeEnd = 0.22
circle.layer.addSublayer(progressCircle)
self.view.addSubview(circle)
Reference: See Here.
Swift 3 use this,
CAShapeLayer with Animation : Continue with Zaid Pathan ans.
let circle = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
circle.layoutIfNeeded()
var progressCircle = CAShapeLayer()
let centerPoint = CGPoint (x: circle.bounds.width / 2, y: circle.bounds.width / 2)
let circleRadius : CGFloat = circle.bounds.width / 2 * 0.83
let circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.5 * M_PI), endAngle: CGFloat(1.5 * M_PI), clockwise: true )
progressCircle = CAShapeLayer ()
progressCircle.path = circlePath.cgPath
progressCircle.strokeColor = UIColor.green.cgColor
progressCircle.fillColor = UIColor.clear.cgColor
progressCircle.lineWidth = 2.5
progressCircle.strokeStart = 0
progressCircle.strokeEnd = 1.0
circle.layer.addSublayer(progressCircle)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1.0
animation.duration = 5.0
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
progressCircle.add(animation, forKey: "ani")
self.view.addSubview(circle)
Here a Swift example of how to make a simple, not closed(to leave space for long numbers) circular progress bar with rounded corners and animation.
open_circular_progress_bar.jpg
func drawBackRingFittingInsideView(lineWidth: CGFloat, lineColor: UIColor) {
let halfSize:CGFloat = min( bounds.size.width/2, bounds.size.height/2)
let desiredLineWidth:CGFloat = lineWidth
let circle = CGFloat(Double.pi * 2)
let startAngle = CGFloat(circle * 0.1)
let endAngle = circle – startAngle
let circlePath = UIBezierPath(
arcCenter: CGPoint(x:halfSize, y:halfSize),
radius: CGFloat( halfSize – (desiredLineWidth/2) ),
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
let shapeBackLayer = CAShapeLayer()
shapeBackLayer.path = circlePath.cgPath
shapeBackLayer.fillColor = UIColor.clear.cgColor
shapeBackLayer.strokeColor = lineColor.cgColor
shapeBackLayer.lineWidth = desiredLineWidth
shapeBackLayer.lineCap = .round
layer.addSublayer(shapeBackLayer)
}
And the animation function.
func animateCircle(duration: TimeInterval) {
let animation = CABasicAnimation(keyPath: “strokeEnd”)
animation.duration = duration
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
shapeLayer.strokeEnd = 1.0
shapeLayer.add(animation, forKey: “animateCircle”)
}
There is a good blog with examples.

cylindrical look Gradient in UIView

I want to draw bar with a cylindrical look and with shadow effect(As shown in the below image). Can some one help to define Linear Gradient for below look and feel.
Code:
-(void)drawRect:(CGRect)rect {
//UIview Back color is red or green
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorspace;
CGContextRef currentContext = UIGraphicsGetCurrentContext();
size_t num_locations = 4;
CGFloat locations[4] = { 0.0,0.7,0.9,1.0 };
CGFloat components[16] = { 0.0, 0.0, 0.0, 0.01,
0.0, 0.0, 0.0, 0.1,
0.0, 0.0, 0.0, 0.2,
0.0, 0.0, 0.0, 0.5
};
rgbColorspace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGRect currentBounds = self.bounds;
CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), currentBounds.size.height);
CGContextDrawLinearGradient(currentContext, glossGradient, topCenter, midCenter, 0);
CGGradientRelease(glossGradient);
CGColorSpaceRelease(rgbColorspace);
}
Here's a general purpose gradient maker:
CGGradientRef makeGradient2(NSArray *colors, CGFloat *stops)
{
size_t nc = 4;
NSInteger colorSize = nc * sizeof(CGFloat);
CGFloat *theComponents = malloc( [colors count] * colorSize );
CGFloat *compPtr = theComponents;
for (int i=0; i < colors.count; i++){
UIColor *color = [colors objectAtIndex:i];
CGColorRef cgColor = [color CGColor];
const CGFloat *components = CGColorGetComponents(cgColor);
memmove(compPtr, components, colorSize);
compPtr += nc;
}
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents (cs, theComponents, stops, colors.count);
CGColorSpaceRelease(cs);
free(theComponents);
return gradient;
}
call it like (this creates a three part gray gradient with lightest in the middle)
CGFloat stops[] = { 0.0, 0.5, 1.0};
gradient = makeGradient2([NSArray arrayWithObjects:
[UIColor colorWithRed:.2 green:.2 blue:.2 alpha:1.0],
[UIColor colorWithRed:.5 green:.5 blue:.5 alpha:1.0],
[UIColor colorWithRed:.2 green:.2 blue:.2 alpha:1.0],
nil], stops);
and then you can draw with it like (for a left to right gradient):
CGContextDrawLinearGradient(gc, gradient, CGPointMake(0.0, 0.0), CGPointMake(rect.size.width, 0.0), 0);