I try to get a random color for UILabel...
- (UIColor *)randomColor
{
int red = arc4random() % 255 / 255.0;
int green = arc4random() % 255 / 255.0;
int blue = arc4random() % 255 / 255.0;
UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
NSLog(#"%#", color);
return color;
}
And use it:
[mat addAttributes:#{NSForegroundColorAttributeName : [self randomColor]} range:range];
But color is always black. What is wrong?
[UIColor colorWithHue:drand48() saturation:1.0 brightness:1.0 alpha:1.0];
or in Swift:
UIColor(hue: CGFloat(drand48()), saturation: 1, brightness: 1, alpha: 1)
Feel free to randomise or adjust saturation and brightness to your liking.
Because you have assigned the colour values to int variables. Use float
(or CGFloat) instead. Also (as #stackunderflow's said), the remainder must
be taken modulo 256 in order to cover the whole range 0.0 ... 1.0:
CGFloat red = arc4random() % 256 / 255.0;
// Or (recommended):
CGFloat red = arc4random_uniform(256) / 255.0;
Here is a swift version, made into a UIColor extension:
extension UIColor {
class func randomColor(randomAlpha: Bool = false) -> UIColor {
let redValue = CGFloat(arc4random_uniform(255)) / 255.0;
let greenValue = CGFloat(arc4random_uniform(255)) / 255.0;
let blueValue = CGFloat(arc4random_uniform(255)) / 255.0;
let alphaValue = randomAlpha ? CGFloat(arc4random_uniform(255)) / 255.0 : 1;
return UIColor(red: redValue, green: greenValue, blue: blueValue, alpha: alphaValue)
}
}
Swift solution using a class var random:
extension UIColor {
class var random: UIColor {
return UIColor(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1), alpha: 1.0)
}
}
Use just like any other in-built UIColor class variable (.red, .blue, .white, etc.), for example:
view.backgroundColor = .random
try this
CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0
CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white
CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black
UIColor *color = [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1];
The following works on iOS 12
NSInteger aRedValue = arc4random()%255;
NSInteger aGreenValue = arc4random()%255;
NSInteger aBlueValue = arc4random()%255;
UIColor *randColor = [UIColor colorWithRed:aRedValue/255.0f green:aGreenValue/255.0f blue:aBlueValue/255.0f alpha:1.0f];
// RGB
UIColor *randomRGBColor = [[UIColor alloc] initWithRed:arc4random()%256/256.0
green:arc4random()%256/256.0
blue:arc4random()%256/256.0
alpha:1.0];
// HSB
UIColor *randomHSBColor = [[UIColor alloc] initWithHue:arc4random()%256/256.0
saturation:(arc4random()%128/256.0)+0.5
brightness:(arc4random()%128/256.0)+0.5
alpha:1.0];
arc4random() % 255 / 255.0 will always be truncated to 0 because arc4random()%255 will be an integer between 0 and 254 inclusive, and dividing by 255.0 and casting to an int will always result in 0. You should save the result as a float instead.
(Also you should use arc4random()%256 if you wish to select randomly from all possible colors.)
Here is a snippet:
CGFloat redLevel = rand() / (float) RAND_MAX;
CGFloat greenLevel = rand() / (float) RAND_MAX;
CGFloat blueLevel = rand() / (float) RAND_MAX;
self.view.backgroundColor = [UIColor colorWithRed: redLevel
green: greenLevel
blue: blueLevel
alpha: 1.0];
Removes duplication of the 3 arc4random lines :)
static func randomColor() -> UIColor {
let random = {CGFloat(arc4random_uniform(255)) / 255.0}
return UIColor(red: random(), green: random(), blue: random(), alpha: 1)
}
Related
What's wrong with my code?
- (IBAction)randomColour:(id)sender {
CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0
CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white
CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black
UIColor *color = [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1];
self.Label.textColor = [UIColor color];
self.Label2.textColor = [UIColor color];
}
I have tried initialising the color variables and UIColor everywhere but I keep getting below error:
no known class method for selector 'color'
even though it's right there!
self.Label.textColor = color; self.Label2.textColor = color;
is there an easy way to get this grid background? Or do I have to do it something like this [NSColor colorWithPatternImage:[NSImage ...]]?
I don't want complete code. I just want to know if there is an easy way to do that and if yes how.
This is my solution:
- (void)drawRect:(NSRect)dirtyRect
{
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
[[NSColor whiteColor] setFill];
CGContextFillRect(context, dirtyRect);
for (int i = 1; i < [self bounds].size.height / 10; i++) {
if (i % 10 == 0) {
[[NSColor colorWithSRGBRed:100/255.0 green:149/255.0 blue:237/255.0 alpha:0.3] set];
} else if (i % 5 == 0) {
[[NSColor colorWithSRGBRed:100/255.0 green:149/255.0 blue:237/255.0 alpha:0.2] set];
} else {
[[NSColor colorWithSRGBRed:100/255.0 green:149/255.0 blue:237/255.0 alpha:0.1] set];
}
[NSBezierPath strokeLineFromPoint:NSMakePoint(0, i * 10 - 0.5) toPoint:NSMakePoint([self bounds].size.width, i * 10 - 0.5)];
}
for (int i = 1; i < [self bounds].size.width / 10; i++) {
if (i % 10 == 0) {
[[NSColor colorWithSRGBRed:100/255.0 green:149/255.0 blue:237/255.0 alpha:0.3] set];
} else if (i % 5 == 0) {
[[NSColor colorWithSRGBRed:100/255.0 green:149/255.0 blue:237/255.0 alpha:0.2] set];
} else {
[[NSColor colorWithSRGBRed:100/255.0 green:149/255.0 blue:237/255.0 alpha:0.1] set];
}
[NSBezierPath strokeLineFromPoint:NSMakePoint(i * 10 - 0.5, 0) toPoint:NSMakePoint(i * 10 - 0.5, [self bounds].size.height)];
}
}
The same but for Swift 4
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
if let context = NSGraphicsContext.current?.cgContext {
NSColor.white.setFill()
context.fill(dirtyRect)
context.flush()
}
for i in 1...(Int(self.bounds.size.height) / 10) {
if i % 10 == 0 {
NSColor.init(red: 100/255.0, green: 149/255.0, blue: 237/255.0, alpha: 0.3).set()
}else if i % 5 == 0 {
NSColor.init(red: 100/255.0, green: 149/255.0, blue: 237/255.0, alpha: 0.2).set()
}else{
NSColor.init(red: 100/255.0, green: 149/255.0, blue: 237/255.0, alpha: 0.1).set()
}
NSBezierPath.strokeLine(from: CGPoint(x: 0, y: CGFloat(i) * 10 - 0.5), to: CGPoint(x: self.bounds.size.width, y: CGFloat(i) * 10 - 0.5))
}
for i in 1...(Int(self.bounds.size.width) / 10) {
if i % 10 == 0 {
NSColor.init(red: 100/255.0, green: 149/255.0, blue: 237/255.0, alpha: 0.3).set()
}else if i % 5 == 0 {
NSColor.init(red: 100/255.0, green: 149/255.0, blue: 237/255.0, alpha: 0.2).set()
}else{
NSColor.init(red: 100/255.0, green: 149/255.0, blue: 237/255.0, alpha: 0.1).set()
}
NSBezierPath.strokeLine(from: CGPoint(x: CGFloat(i) * 10 - 0.5, y:0), to: CGPoint(x: CGFloat(i) * 10 - 0.5, y: self.bounds.size.height))
}
}
I don't think a pattern color is a good solution here, especially since you need to vary the lines. Instead use an NSBezierPath and moveToPoint/lineToPoint pairs for each horizontal and vertical line. You can then draw the grid in one call. Do extra steps for those lines with a different color (alpha) and/or width (i.e. don't add the thicker lines to the main grid path but create a separate one for them).
Just have a look at the Sketch example that comes with every Xcode installation or is available as a separate download, too.
It features a grid implementation (and many other useful demonstrations of Cocoa techniques..) allowing for zooming, etc.
Porting Methanol's solution for swift and resolving error in MacOS 10.10 onwards.
A simple static function could be used and called from drawRect()
static func makeGridBackground(dirtyRect: NSRect, view: NSView){
//view.print("WMEditorUtils: initiated drawing")
//Fill background with white color
if let context = NSGraphicsContext.currentContext()?.CGContext {
NSColor.whiteColor().setFill()
CGContextFillRect(context, dirtyRect)
CGContextFlush(context)
}
//Draw Lines: Horizontal
for var i:Int = 1; i < (Int)(view.bounds.size.height / 10); i++ {
if (i % 10 == 0) {
NSColor(deviceRed: 100.0/255.0, green: 149.0/255.0, blue: 237.0/255.0, alpha: 0.3).set()
}
else if (i % 5 == 0) {
NSColor(deviceRed: 100.0/255.0, green: 149.0/255.0, blue: 237.0/255.0, alpha: 0.2).set()
}
else{
NSColor(deviceRed: 100.0/255.0, green: 149.0/255.0, blue: 237.0/255.0, alpha: 0.1).set()
}
NSBezierPath.strokeLineFromPoint(NSMakePoint(0, (CGFloat)(i * 10) - 0.5), toPoint: NSMakePoint(view.bounds.size.width, (CGFloat)(i * 10) - 0.5))
}
//Draw Lines: Vertical
for var i:Int = 1; i < (Int)(view.bounds.size.width / 10); i++ {
if (i % 10 == 0) {
NSColor(deviceRed: 100.0/255.0, green: 149.0/255.0, blue: 237.0/255.0, alpha: 0.3).set()
}
else if (i % 5 == 0) {
NSColor(deviceRed: 100.0/255.0, green: 149.0/255.0, blue: 237.0/255.0, alpha: 0.2).set()
}
else{
NSColor(deviceRed: 100.0/255.0, green: 149.0/255.0, blue: 237.0/255.0, alpha: 0.1).set()
}
NSBezierPath.strokeLineFromPoint(NSMakePoint((CGFloat)(i * 10) - 0.5, 0), toPoint: NSMakePoint((CGFloat)(i * 10) - 0.5, view.bounds.size.width))
}
}
The same of "sad tuna" but in Swift 3
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
if let context = NSGraphicsContext.currentContext()?.CGContext {
NSColor.whiteColor().setFill()
CGContextFillRect(context, dirtyRect)
CGContextFlush(context)
}
for i in 1...(Int(self.bounds.size.height) / 10) {
if i % 10 == 0 {
NSColor(SRGBRed: 100/255.0, green: 149/255.0, blue: 237/255.0, alpha: 0.3).set()
}else if i % 5 == 0 {
NSColor(SRGBRed: 100/255.0, green: 149/255.0, blue: 237/255.0, alpha: 0.2).set()
}else{
NSColor(SRGBRed: 100/255.0, green: 149/255.0, blue: 237/255.0, alpha: 0.1).set()
}
NSBezierPath.strokeLineFromPoint(CGPointMake(0, CGFloat(i) * 10 - 0.5), toPoint: CGPointMake(self.bounds.size.width, CGFloat(i) * 10 - 0.5))
}
for i in 1...(Int(self.bounds.size.width) / 10) {
if i % 10 == 0 {
NSColor(SRGBRed: 100/255.0, green: 149/255.0, blue: 237/255.0, alpha: 0.3).set()
}else if i % 5 == 0 {
NSColor(SRGBRed: 100/255.0, green: 149/255.0, blue: 237/255.0, alpha: 0.2).set()
}else{
NSColor(SRGBRed: 100/255.0, green: 149/255.0, blue: 237/255.0, alpha: 0.1).set()
}
NSBezierPath.strokeLineFromPoint(CGPointMake(CGFloat(i) * 10 - 0.5, 0), toPoint: CGPointMake(CGFloat(i) * 10 - 0.5, self.bounds.size.height))
}
}
I am making a military strategy game. When an army in my game takes over a territory, I want to be able to change the color of the territory on the map so that it shows the new controller of that territory. Here is the code I have that changes a territory's image:
I would write this line for example:
UIImage *newImg = [self imageOfTerritoryWithNewArmy:#"japan" AndOldTerritoryImage:[UIImage imageNamed:#"ireland.gif"]];
Here is the rest of the code:
-(UIImage *)createImageWithRGB:(NSArray *)colorData width:(NSInteger)width height:(NSInteger)height{
unsigned char *rawData = malloc(width*height*4);
for (int i=0; i<width*height; ++i)
{
CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
UIColor *color = colorData[i];
if ([color respondsToSelector:#selector(getRed:green:blue:alpha:)]) {
[color getRed:&red green:&green blue:&blue alpha:&alpha];
} else {
const CGFloat *components = CGColorGetComponents(color.CGColor);
red = components[0];
green = components[1];
blue = components[2];
alpha = components[3];
}
if(alpha > 0){
rawData[4*i] = red * 255;
rawData[4*i+1] = green * 255;
rawData[4*i+2] = blue * 255;
rawData[4*i+3] = alpha * 255;
}
else{
rawData[4*i] = 255;
rawData[4*i+1] = 255;
rawData[4*i+2] = 255;
rawData[4*i+3] = 0;
}
}
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL,
rawData,
width*height*4,
NULL);
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGImageRef imageRef = CGImageCreate(width,
height,
8,
32,
4*width,colorSpaceRef,
bitmapInfo,
provider,NULL,NO,renderingIntent);
UIImage *newImage = [UIImage imageWithCGImage:imageRef];
return newImage;
}
- (NSArray *)getRGBAsFromImage:(UIImage*)image atX:(int)xx andY:(int)yy count:(int)count{
NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
// First get the image into your data buffer
CGImageRef imageRef = [image CGImage];
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
// Now your rawData contains the image data in the RGBA8888 pixel format.
int byteIndex = (bytesPerRow * yy) + xx * bytesPerPixel;
for (int ii = 0 ; ii < count ; ++ii)
{
CGFloat red = (rawData[byteIndex] * 1.0) / 255.0;
CGFloat green = (rawData[byteIndex + 1] * 1.0) / 255.0;
CGFloat blue = (rawData[byteIndex + 2] * 1.0) / 255.0;
CGFloat alpha = (rawData[byteIndex + 3] * 1.0) / 255.0;
byteIndex += 4;
UIColor *acolor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
[result addObject:acolor];
}
free(rawData);
return result;
}
-(UIColor *)colorOfArmy:(NSString *)army{
UIColor *color;
army = [army stringByReplacingOccurrencesOfString:#"\a" withString:#""];
if([army isEqual:#"france"]){
color = [[UIColor alloc] initWithRed:0.3137254 green:0.3686274 blue:0.9058823 alpha:1];
}
if([army isEqual:#"germany"]){
color = [[UIColor alloc] initWithRed:0.6352941 green:0.4313725 blue:0.3372549 alpha:1];
}
if([army isEqual:#"uk"]){
color = [[UIColor alloc] initWithRed:0.8941176 green:0.4235294 blue:0.4941176 alpha:1];
}
if([army isEqual:#"italy"]){
color = [[UIColor alloc] initWithRed:0.5137254 green:0.1215686 blue:0.4745098 alpha:1];
}
if([army isEqual:#"ussr"]){
color = [[UIColor alloc] initWithRed:0.3607843 green:0.0823529 blue:0.1215686 alpha:1];
}
if([army isEqual:#"japan"]){
color = [[UIColor alloc] initWithRed:0.9215686 green:0.6156862 blue:0.3137254 alpha:1];
}
if([army isEqual:#""]){
color = [[UIColor alloc] initWithRed:0.1215686 green:0.2823529 blue:0.1607843 alpha:1];
}
if(color == nil){
NSLog(#"the problem was %#", army);
}
return color;
}
-(UIImage *)imageOfTerritoryWithNewArmy:(NSString *)army AndOldTerritoryImage:(UIImage *)oldImg{
CGImageRef image = oldImg.CGImage;
NSInteger width = CGImageGetWidth(image);
NSInteger height = CGImageGetHeight(image);
NSArray *rgba = [self getRGBAsFromImage:oldImg atX:0 andY:0 count:width * height];
NSMutableArray *fixedRGBA = [[NSMutableArray alloc] init];
UIColor *armyColor = [self colorOfArmy:army];
for(UIColor *pixel in rgba){
CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
if ([pixel respondsToSelector:#selector(getRed:green:blue:alpha:)]) {
[pixel getRed:&red green:&green blue:&blue alpha:&alpha];
} else {
const CGFloat *components = CGColorGetComponents(pixel.CGColor);
red = components[0];
green = components[1];
blue = components[2];
alpha = components[3];
}
red = red * 255;
green = green * 255;
blue = blue * 255;
if(alpha > 0){
if(red < 50 && green < 50 && blue < 50){
[fixedRGBA addObject:pixel];
}
else{
[fixedRGBA addObject:armyColor];
}
}
else{
[fixedRGBA addObject:pixel];
}
}
return [self createImageWithRGB:fixedRGBA width:width height:height];
}
The problem that I am having is that when the image is drawn again, all of the pixels that used to be blank because they have an alpha value of 0 are displayed as white. How can I get these pixels to still be displayed as clear pixels?
When creating the image, you should specify that it contains an alpha channel. That is, instead of:
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
use:
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast;
See the CGImage interface specification for more info.
I believe your else clause here is where you want to set the color to clear
if(alpha > 0){
rawData[4*i] = red * 255;
rawData[4*i+1] = green * 255;
rawData[4*i+2] = blue * 255;
rawData[4*i+3] = alpha * 255;
}
else{
rawData[4*i] = 255;
rawData[4*i+1] = 255;
rawData[4*i+2] = 255;
rawData[4*i+3] = 255;
}
You have to change the last assignment to 0
if(alpha > 0){
rawData[4*i] = red * 255;
rawData[4*i+1] = green * 255;
rawData[4*i+2] = blue * 255;
rawData[4*i+3] = alpha * 255;
}
else{
rawData[4*i] = 255;
rawData[4*i+1] = 255;
rawData[4*i+2] = 255;
rawData[4*i+3] = 0;
}
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.
I'm trying to adapt an example provided by Apple in order to programmatically draw stars in line, the code is the following:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, aSize);
for (NSUInteger i=0; i<stars; i++)
{
CGContextSetFillColorWithColor(context, aColor);
CGContextSetStrokeColorWithColor(context, aColor);
float w = item.size.width;
double r = w / 2;
double theta = 2 * M_PI * (2.0 / 5.0); // 144 degrees
CGContextMoveToPoint(context, 0, r);
for (NSUInteger k=1; k<5; k++)
{
float x = r * sin(k * theta);
float y = r * cos(k * theta);
CGContextAddLineToPoint(context, x, y);
}
CGContextClosePath(context);
CGContextFillPath(context);
}
The code above draws a perfect star, but is 1. displayed upside down 2. is black and without border. What I want to achive is to draw many stars on the same line and with the given style. I understand that I'm actually drawing the same path 5 times in the same position and that I have somehow to flip the context vertically, but after several tests I gave up! (I lack the necessary math and geometry skills :P)... could you please help me?
UPDATE:
Ok, thanks to CocoaFu, this is my refactored and working draw utility:
- (void)drawStars:(NSUInteger)count inContext:(CGContextRef)context;
{
// constants
const float w = self.itemSize.width;
const float r = w/2;
const double theta = 2 * M_PI * (2.0 / 5.0);
const float flip = -1.0f; // flip vertically (default star representation)
// drawing center for the star
float xCenter = r;
for (NSUInteger i=0; i<count; i++)
{
// get star style based on the index
CGContextSetFillColorWithColor(context, [self fillColorForItemAtIndex:i]);
CGContextSetStrokeColorWithColor(context, [self strokeColorForItemAtIndex:i]);
// update position
CGContextMoveToPoint(context, xCenter, r * flip + r);
// draw the necessary star lines
for (NSUInteger k=1; k<5; k++)
{
float x = r * sin(k * theta);
float y = r * cos(k * theta);
CGContextAddLineToPoint(context, x + xCenter, y * flip + r);
}
// update horizontal center for the next star
xCenter += w + self.itemMargin;
// draw current star
CGContextClosePath(context);
CGContextFillPath(context);
CGContextStrokePath(context);
}
}
Here is code that will draw 3 stars in a horizontal line, it's is not pretty but it may help:
-(void)drawRect:(CGRect)rect
{
int aSize = 100.0;
const CGFloat color[4] = { 0.0, 0.0, 1.0, 1.0 }; // Blue
CGColorRef aColor = CGColorCreate(CGColorSpaceCreateDeviceRGB(), color);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, aSize);
CGFloat xCenter = 100.0;
CGFloat yCenter = 100.0;
float w = 100.0;
double r = w / 2.0;
float flip = -1.0;
for (NSUInteger i=0; i<3; i++)
{
CGContextSetFillColorWithColor(context, aColor);
CGContextSetStrokeColorWithColor(context, aColor);
double theta = 2.0 * M_PI * (2.0 / 5.0); // 144 degrees
CGContextMoveToPoint(context, xCenter, r*flip+yCenter);
for (NSUInteger k=1; k<5; k++)
{
float x = r * sin(k * theta);
float y = r * cos(k * theta);
CGContextAddLineToPoint(context, x+xCenter, y*flip+yCenter);
}
xCenter += 150.0;
}
CGContextClosePath(context);
CGContextFillPath(context);
}
Here's an algorithm to implement what buddhabrot implied:
- (void)drawStarInContext:(CGContextRef)context withNumberOfPoints:(NSInteger)points center:(CGPoint)center innerRadius:(CGFloat)innerRadius outerRadius:(CGFloat)outerRadius fillColor:(UIColor *)fill strokeColor:(UIColor *)stroke strokeWidth:(CGFloat)strokeWidth {
CGFloat arcPerPoint = 2.0f * M_PI / points;
CGFloat theta = M_PI / 2.0f;
// Move to starting point (tip at 90 degrees on outside of star)
CGPoint pt = CGPointMake(center.x - (outerRadius * cosf(theta)), center.y - (outerRadius * sinf(theta)));
CGContextMoveToPoint(context, pt.x, pt.y);
for (int i = 0; i < points; i = i + 1) {
// Calculate next inner point (moving clockwise), accounting for crossing of 0 degrees
theta = theta - (arcPerPoint / 2.0f);
if (theta < 0.0f) {
theta = theta + (2 * M_PI);
}
pt = CGPointMake(center.x - (innerRadius * cosf(theta)), center.y - (innerRadius * sinf(theta)));
CGContextAddLineToPoint(context, pt.x, pt.y);
// Calculate next outer point (moving clockwise), accounting for crossing of 0 degrees
theta = theta - (arcPerPoint / 2.0f);
if (theta < 0.0f) {
theta = theta + (2 * M_PI);
}
pt = CGPointMake(center.x - (outerRadius * cosf(theta)), center.y - (outerRadius * sinf(theta)));
CGContextAddLineToPoint(context, pt.x, pt.y);
}
CGContextClosePath(context);
CGContextSetLineWidth(context, strokeWidth);
[fill setFill];
[stroke setStroke];
CGContextDrawPath(context, kCGPathFillStroke);
}
Works for me for most basic stars. Tested from 2 points (makes a good diamond!) up to 9 stars.
If you want a star with point down, change the subtraction to addition.
To draw multiples, create a loop and call this method multiple times, passing a new center each time. That should line them up nicely!
I prefer using a CAShaperLayer to implementing drawRect, as it can then be animated.
Here's a function that will create a path in the shape of a 5 point star:
func createStarPath(size: CGSize) -> CGPath {
let numberOfPoints: CGFloat = 5
let starRatio: CGFloat = 0.5
let steps: CGFloat = numberOfPoints * 2
let outerRadius: CGFloat = min(size.height, size.width) / 2
let innerRadius: CGFloat = outerRadius * starRatio
let stepAngle = CGFloat(2) * CGFloat(M_PI) / CGFloat(steps)
let center = CGPoint(x: size.width / 2, y: size.height / 2)
let path = CGPathCreateMutable()
for i in 0..<Int(steps) {
let radius = i % 2 == 0 ? outerRadius : innerRadius
let angle = CGFloat(i) * stepAngle - CGFloat(M_PI_2)
let x = radius * cos(angle) + center.x
let y = radius * sin(angle) + center.y
if i == 0 {
CGPathMoveToPoint(path, nil, x, y)
}
else {
CGPathAddLineToPoint(path, nil, x, y)
}
}
CGPathCloseSubpath(path)
return path
}
It can then be used with a CAShapeLayer like so:
let layer = CAShapeLayer()
layer.path = createStarPath(CGSize(width: 100, height: 100))
layer.lineWidth = 1
layer.strokeColor = UIColor.blackColor().CGColor
layer.fillColor = UIColor.yellowColor().CGColor
layer.fillRule = kCAFillRuleNonZero