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.
Related
I'm trying to implement a clock face in UIView - without much luck. The problem is that the hands on the clock face end up in totally the wrong position (i.e. pointing to the wrong time). I'm pretty sure that the logic of my code is correct - but clearly there's something significantly wrong.
My test app is an iOS Single View Application. ViewController.h looks like this:
#import <UIKit/UIKit.h>
#interface Clock : UIView {
UIColor* strokeColour;
UIColor* fillColour;
float strokeWidth;
NSDate* clockTime;
CGPoint clockCentre;
float clockRadius;
}
- (id)initWithCentre:(CGPoint)centre
radius:(float)radius
borderWidth:(float)width
facecolour:(UIColor*)fill
handcolour:(UIColor*)handFill
time:(NSDate*)time;
#end
#interface ViewController : UIViewController
#end
and ViewController.m looks like this:
#import "ViewController.h"
#implementation Clock
- (id)initWithCentre:(CGPoint)centre
radius:(float)radius
borderWidth:(float)width
facecolour:(UIColor*)fill
handcolour:(UIColor*)handFill
time:(NSDate*)time {
CGRect frame = CGRectMake(centre.x-radius,
centre.y-radius,
radius*2,
radius*2);
if (self = [super initWithFrame:frame]) {
// Initialization code
strokeColour = fill;
fillColour = handFill;
strokeWidth = width;
clockRadius = radius;
clockTime = time;
clockCentre.x = frame.size.width/2;
clockCentre.y = frame.size.height/2;
}
return self;
}
- (void)drawRect:(CGRect)rect {
double red, green, blue, alpha;
CGRect circleRect;
circleRect.origin.x = rect.origin.x+strokeWidth;
circleRect.origin.y = rect.origin.y+strokeWidth;
circleRect.size.width = rect.size.width - (strokeWidth*2);
circleRect.size.height = rect.size.height - (strokeWidth*2);
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(contextRef, strokeWidth);
[fillColour getRed:&red green:&green blue:&blue alpha:&alpha];
CGContextSetRGBFillColor(contextRef, red, green, blue, alpha);
[strokeColour getRed:&red green:&green blue:&blue alpha:&alpha];
CGContextSetRGBStrokeColor(contextRef, red, green, blue, alpha);
CGContextFillEllipseInRect(contextRef, circleRect);
CGContextStrokeEllipseInRect(contextRef, circleRect);
CGContextSetLineWidth(contextRef, strokeWidth);
[strokeColour getRed:&red green:&green blue:&blue alpha:&alpha];
CGContextSetRGBStrokeColor(contextRef, red, green, blue, alpha);
float hourAngle, minuteAngle, secondAngle, angle;
double endX, endY;
NSCalendar *cal = [[NSCalendar alloc]initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSUInteger units = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *components = [cal components:units fromDate:clockTime];
hourAngle = (components.hour /12.0) * M_PI * 2.0;
minuteAngle = (components.minute / 60.0) * M_PI * 2.0;
secondAngle = (components.second / 60.0) * M_PI * 2.0;
//minute hand
angle = minuteAngle;
endX = cos(angle) * (clockRadius*0.85) + clockCentre.x;
endY = sin(angle) * (clockRadius*0.85) + clockCentre.y;
CGContextMoveToPoint(contextRef,clockCentre.x,clockCentre.y);
CGContextAddLineToPoint(contextRef,endX,endY);
CGContextStrokePath(contextRef);
//hour hand
angle = hourAngle;
endX = cos(angle) * (clockRadius*0.65) + clockCentre.x;
endY = sin(angle) * (clockRadius*0.65) + clockCentre.y;
CGContextSetLineWidth(contextRef, strokeWidth*1.5);
CGContextMoveToPoint(contextRef,clockCentre.x,clockCentre.y);
CGContextAddLineToPoint(contextRef,endX,endY);
CGContextStrokePath(contextRef);
}
#end
#interface ViewController ()
#end
#implementation ViewController
- (UIView*)drawClockFaceWithCentre:(CGPoint)centre
radius:(float)radius
time:(NSDate*)time
colour:(UIColor*)colour
backgroundColour:(UIColor*)bgcolour {
UIView* clock = [[Clock alloc]initWithCentre:centre
radius:radius
borderWidth:6.0
facecolour:bgcolour
handcolour:colour
time:time];
clock.backgroundColor = [UIColor clearColor];
return clock;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CGPoint centre;
centre.x=200;
centre.y=200;
[self.view addSubview:[self drawClockFaceWithCentre:centre
radius:100
time:[NSDate date]
colour:[UIColor blackColor]
backgroundColour:[UIColor whiteColor]]];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I'm sure it's something very simple that I've done wrong - a mistake in my maths. Can anyone see the bug (and any other suggestions for improvements that could be made would be welcome, bearing in mind (of course) that this is just a simple test harness).
As I wrote in the comment, consider dropping doing everything in drawRect and use layers. Also, instead of calculating trigonometry to draw correct path, consider just drawing hands at 12:00 and transform rotate them with correct angle. You could do it like this in init...
CAShapeLayer *faceLayer = [CAShapeLayer layer];
faceLayer.frame = self.bounds;
faceLayer.fillColor = [UIColor whiteColor].CGColor;
faceLayer.strokeColor = [UIColor blackColor].CGColor;
faceLayer.path = [UIBezierPath bezierPathWithOvalInRect:faceLayer.bounds].CGPath;
faceLayer.lineWidth = 3.0;
[self.layer addSublayer:faceLayer];
CGPoint middle = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSUInteger units = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *components = [cal components:units fromDate:time];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:middle];
[path addLineToPoint:CGPointMake(middle.x, middle.y-radius*0.5)];
CGFloat hourAreaAngle = (2*M_PI)/12.0;
CAShapeLayer *hourLayer = [CAShapeLayer layer];
hourLayer.frame = self.bounds;
hourLayer.strokeColor = [UIColor redColor].CGColor;
hourLayer.lineWidth = 3.0;
hourLayer.path = path.CGPath;
[self.layer addSublayer:hourLayer];
hourLayer.transform = CATransform3DMakeRotation((components.hour/12.0*(M_PI*2.0))+(hourAreaAngle*(components.minute/60.0)), 0.0, 0.0, 1.0);
[path removeAllPoints];
[path moveToPoint:middle];
[path addLineToPoint:CGPointMake(middle.x, middle.y-radius*0.8)];
CAShapeLayer *minuteLayer = [CAShapeLayer layer];
minuteLayer.frame = self.bounds;
minuteLayer.strokeColor = [UIColor blueColor].CGColor;
minuteLayer.lineWidth = 2.0;
minuteLayer.path = path.CGPath;
[self.layer addSublayer:minuteLayer];
minuteLayer.transform = CATransform3DMakeRotation(components.minute/60.0*(M_PI*2.0), 0.0, 0.0, 1.0);
[path removeAllPoints];
[path moveToPoint:middle];
[path addLineToPoint:CGPointMake(middle.x, middle.y-radius)];
CAShapeLayer *secondsLayer = [CAShapeLayer layer];
secondsLayer.frame = self.bounds;
secondsLayer.strokeColor = [UIColor greenColor].CGColor;
secondsLayer.lineWidth = 1.0;
secondsLayer.path = path.CGPath;
[self.layer addSublayer:secondsLayer];
secondsLayer.transform = CATransform3DMakeRotation(components.second/60.0*(M_PI*2.0), 0.0, 0.0, 1.0);
Note I put my colours and line widths because I'm lazy and didn't want to check your code :P.
If you want to update hands later, just store hands layers as variables and transform rotate them again (just remember to first transform to identity!).
I have an arc that I have drawn using CAShapeLayer and I want it to rotate around the circle's center. I am trying to get this animation using a CABasicAnimation on the 'transform.rotation' property. But the rotation seems is happening about a point that is not the same as the circle's center.
-(void)drawRect:(CGRect)rect{
int radius = 100;
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius].CGPath;
circle.fillColor = [UIColor orangeColor].CGColor;
circle.strokeColor = [UIColor blueColor].CGColor;
circle.lineWidth = 5;
circle.strokeStart = 0.0f;
circle.strokeEnd = 0.1f;
[self.layer addSublayer:circle];
CABasicAnimation *spinAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
spinAnimation.byValue = [NSNumber numberWithFloat:2.0f*M_PI];
spinAnimation.duration = 4;
spinAnimation.repeatCount = INFINITY;
[circle addAnimation:spinAnimation forKey:#"indeterminateAnimation"];
}
I know its an old problem, but whats the way out. I have tinkered a lot by setting the layer's bounds or the anchor point but it doesn't rotate about the center yet.
circle.bounds = self.frame;
Here is how I add the view to the view controller.
[self.view addSubview:[[ProgressIndicator alloc] initWithFrame:CGRectMake(50, 50, 200, 200)]];
This is what I eventually did.
The mistake was to add the animation to the sublayer and not the layer.
There is no need to tinker with anchorPoint or position here when the rotation is just needed about the center of the circle.
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
int radius = 50;
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius].CGPath;
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor blueColor].CGColor;
circle.lineWidth = 5;
circle.strokeStart = 0.0f;
circle.strokeEnd = 0.1f;
[self.layer addSublayer:circle];
}
return self; }
- (void)drawRect:(CGRect)rect {
CABasicAnimation *spinAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
spinAnimation.byValue = [NSNumber numberWithFloat:2.0f*M_PI];
spinAnimation.duration = 4;
spinAnimation.repeatCount = INFINITY;
[self.layer addAnimation:spinAnimation forKey:#"indeterminateAnimation"]; }
Think is better to do something like this)
CGFloat center = CGRectGetWidth(frame) / 2;
CGFloat lineWidth = 5;
CGFloat radius = center - lineWidth / 2;
CGRect bounds = frame;
bounds.origin = CGPointZero;
CAShapeLayer *spinLayer = [CAShapeLayer layer];
spinLayer.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(center, center) radius:radius startAngle:0 endAngle:0.2 clockwise:YES].CGPath;
spinLayer.fillColor = [UIColor clearColor].CGColor;
spinLayer.strokeColor = [UIColor blueColor].CGColor;
spinLayer.lineWidth = 5;
spinLayer.lineCap = kCALineCapRound;
spinLayer.bounds = bounds;
spinLayer.position = CGPointMake(center, center);
[self.layer insertSublayer:spinLayer above:nil];
And animation:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.byValue = [NSNumber numberWithFloat:2.f * M_PI];
animation.duration = 1.5f;
animation.repeatCount = INFINITY;
[spinLayer addAnimation:animation forKey:#"lineRotation"];
[self setNeedsLayout];
Swift 3
var bounds = frame
bounds.origin = CGPoint.zero
let center: CGFloat = frame.size.width
let lineWidth: CGFloat = 5
let radius = center - lineWidth / 2
let spinLayer = CAShapeLayer()
spinLayer.path = UIBezierPath(arcCenter: CGPoint(x: center, y: center),
radius: radius,
startAngle: 0,
endAngle: 0.2,
clockwise: true).cgPath
spinLayer.strokeColor = UIColor.blue.cgColor
spinLayer.fillColor = UIColor.clear.cgColor
spinLayer.lineWidth = lineWidth
spinLayer.lineCap = kCALineCapRound
spinLayer.bounds = bounds
spinLayer.position = CGPoint(x: center, y: center)
view.layer.insertSublayer(spinLayer, above: nil)
let rotation = CABasicAnimation(keyPath: "transform.rotation")
rotation.byValue = NSNumber(value: 2*M_PI as Double)
rotation.duration = 1
rotation.repeatCount = Float.infinity
spinLayer.add(rotation, forKey: "lineRotation")
I have a series of UIImageViews all hooked up in IBOutlet collection that I would like to move at whatever angle they are facing at a set acceleration speed.
The first thing I do is randomly put them at an angle (ie: 35, 90, 70, 270 degrees) and now I would like to move them.
The images are icons of planes and I would like to move each plane icon at a certain angle (whatever angle they are currently facing) and at a certain acceleration (say 2-4 pixels or something).
The only issue is I am not really sure how to move a UIImage at a given angle at a given acceleration.
Is there are a way to do this with Core Animation?
Many thanks
Update:
I rewrote the method with seperate Objects to handle the Airplanes themselves and try to use UIBezierPath;
// Angles for airplane icons
VICAirplane *plane1 = [[VICAirplane alloc] initWithX:48 withY:104 withAngle:140 withSpeed:5];
VICAirplane *plane2 = [[VICAirplane alloc] initWithX:50 withY:250 withAngle:60 withSpeed:7];
VICAirplane *plane3 = [[VICAirplane alloc] initWithX:280 withY:75 withAngle:240 withSpeed:12];
VICAirplane *plane4 = [[VICAirplane alloc] initWithX:230 withY:270 withAngle:120 withSpeed:3];
VICAirplane *plane5 = [[VICAirplane alloc] initWithX:148 withY:225 withAngle:85 withSpeed:10];
VICAirplane *plane6 = [[VICAirplane alloc] initWithX:175 withY:225 withAngle:85 withSpeed:8];
self.airplanes = #[plane1,plane2,plane3,plane4,plane5,plane6];
UIImage *sprite = [UIImage imageNamed:#"Plane Shadow"];
CGFloat spriteWidth = sprite.size.width;
CGFloat spriteHeight = sprite.size.height;
for (VICAirplane *planeObj in self.airplanes) {
CALayer *plane = [CALayer layer];
plane.bounds = CGRectMake(0, 0, spriteWidth, spriteHeight);
plane.position = CGPointMake(planeObj.x, planeObj.y);
plane.contents = (id)(sprite.CGImage);
CGAffineTransform rotateTransform = CGAffineTransformMakeRotation(planeObj.angle);
[plane setAffineTransform:rotateTransform];
[self.view.layer addSublayer:plane];
// Animate it
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(100.0, 100.0)];
[path addLineToPoint:CGPointMake(200.0, 200.0)];
[path addLineToPoint:CGPointMake(100.0, 300.0)];
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = path.CGPath;
anim.rotationMode = kCAAnimationRotateAuto;
anim.repeatCount = HUGE_VALF;
anim.duration = 8.0;
anim.autoreverses = YES;
[plane addAnimation:anim forKey:#"race"];
}
What I'd like to do is generate UIBezierPath based off the angle of each plane and move it at the speed
but I don't think moveToPoint will help solve this
Thanks
I have a potental alternative solution which I am now using/working on;
// Angles for airplane icons
_sprite = [UIImage imageNamed:#"Plane"];
CGFloat spriteWidth = _sprite.size.width;
CGFloat spriteHeight = _sprite.size.height;
self.velocity=1.0;
//Random x&y points
CGFloat x;
CGFloat y;
//VICAirplane *plane1 = [[VICAirplane alloc] initWithX:48 withY:104 withAngle:0 withSpeed:10];
x = (CGFloat) (arc4random() % (int) self.view.frame.size.width);
y = (CGFloat) (arc4random() % (int) self.view.frame.size.height);
UIImageView *theBug = [[UIImageView alloc] initWithFrame:CGRectMake(x, y, spriteWidth, spriteHeight)];
[theBug setBackgroundColor:[UIColor redColor]];
[theBug setImage:_sprite];
[self.view addSubview:theBug];
double delayInSeconds = 1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
//Calculate the angle to rotate
CGFloat newX = (CGFloat) (arc4random() % (int) self.view.frame.size.width);
CGFloat newY = (CGFloat) (arc4random() % (int) self.view.frame.size.height);
CGPoint rotateToLocation = CGPointMake(newX, newY);
theBug.layer.anchorPoint = CGPointMake(0.5, 0);
float angleToRotate = atan2f(theBug.transform.tx-newX, theBug.transform.ty - newY);
if (theBug.transform.tx < newX)
angleToRotate *= 1;
[UIView animateWithDuration:4
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
theBug.transform = CGAffineTransformRotate(theBug.transform, angleToRotate);
} completion:^(BOOL finished) {
[UIView animateWithDuration:2
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
theBug.center = rotateToLocation;
} completion:^(BOOL finished) {
NSLog(#"Finished");
}];
}];
});
This is my solution for the issue. Many thanks.
I've looked at other SO answers to this quesiton, particularly Brad Larson's but I am still not getting any results. I'm not sure if I am approaching this the correct way. In my app, when a user touches the screen I capture the point and create an arc from a fixed point to that touch point. I then want to move a UIView along that arc.
Here's my code:
ViewController.m
//method to "shoot" object - KIP_Projectile creates the arc, KIP_Character creates the object I want to move along the arc
...
//get arc for trajectory
KIP_Projectile* vThisProjectile = [[KIP_Projectile alloc] initWithFrame:CGRectMake(51.0, fatElvisCenterPoint-30.0, touchPoint.x, 60.0)];
vThisProjectile.backgroundColor = [UIColor clearColor];
[self.view addSubview:vThisProjectile];
...
KIP_Character* thisChar = [[KIP_Character alloc] initWithFrame:CGRectMake(51, objCenterPoint-5, imgThisChar.size.width, imgThisChar.size.height)];
thisChar.backgroundColor = [UIColor clearColor];
thisChar.charID = charID;
thisChar.charType = 2;
thisChar.strCharType = #"Projectile";
thisChar.imgMyImage = imgThisChar;
thisChar.myArc = vThisProjectile;
[thisChar buildImage];
[thisChar traceArc];
in KIP_Projectile I build the arc using this code:
- (CGMutablePathRef) createArcPathFromBottomOfRect : (CGRect) rect : (CGFloat) arcHeight {
CGRect arcRect = CGRectMake(rect.origin.x, rect.origin.y + rect.size.height - arcHeight, rect.size.width, arcHeight);
CGFloat arcRadius = (arcRect.size.height/2) + (pow(arcRect.size.width, 2) / (8*arcRect.size.height));
CGPoint arcCenter = CGPointMake(arcRect.origin.x + arcRect.size.width/2, arcRect.origin.y + arcRadius);
CGFloat angle = acos(arcRect.size.width / (2*arcRadius));
CGFloat startAngle = radians(180) + angle;
CGFloat endAngle = radians(360) - angle;
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path, NULL, arcCenter.x, arcCenter.y, arcRadius, startAngle, endAngle, 0);
return path;
}
- (void)drawRect:(CGRect)rect {
CGContextRef currentContext = UIGraphicsGetCurrentContext();
_myArcPath = [self createArcPathFromBottomOfRect:self.bounds:30.0];
CGContextSetLineWidth(currentContext, 1);
CGFloat red[4] = {1.0f, 0.0f, 0.0f, 1.0f};
CGContextSetStrokeColor(currentContext, red);
CGContextAddPath(currentContext, _myArcPath);
CGContextStrokePath(currentContext);
}
Works fine. The arc is displayed with a red stroke on the screen.
In KIP_Character, which has been passed it's relevant arc, I am using this code but getting no results.
- (void) traceArc {
CGMutablePathRef myArc = _myArc.myArcPath;
// Set up path movement
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.path = myArc;
CGPathRelease(myArc);
[self.layer addAnimation:pathAnimation forKey:#"savingAnimation"];
}
Any help here would be appreciated.
Is it possible create an UIImage or an UIImageView with rounded corners? Because I want take an UIImage and show it inside an UIImageView, but I don't know how to do it.
Yes, it is possible.
Import the QuartzCore (#import <QuartzCore/QuartzCore.h>) header and play with the layer property of the UIImageView.
yourImageView.layer.cornerRadius = yourRadius;
yourImageView.clipsToBounds = YES;
See the CALayer class reference for more info.
Try this Code For Round Image Import QuartzCore framework
simple way to create Round Image
imageView.layer.backgroundColor=[[UIColor clearColor] CGColor];
imageView.layer.cornerRadius=20;
imageView.layer.borderWidth=2.0;
imageView.layer.masksToBounds = YES;
imageView.layer.borderColor=[[UIColor redColor] CGColor];
Objective-C
-(UIImage *)makeRoundedImage:(UIImage *) image
radius: (float) radius;
{
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = CGRectMake(0, 0, image.size.width, image.size.height);
imageLayer.contents = (id) image.CGImage;
imageLayer.masksToBounds = YES;
imageLayer.cornerRadius = radius;
UIGraphicsBeginImageContext(image.size);
[imageLayer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return roundedImage;
}
Swift 3
func makeRoundedImage(image: UIImage, radius: Float) -> UIImage {
var imageLayer = CALayer()
imageLayer.frame = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
imageLayer.contents = image.cgImage
imageLayer.masksToBounds = true
imageLayer.cornerRadius = radius
UIGraphicsBeginImageContext(image.size)
imageLayer.render(in: UIGraphicsGetCurrentContext())
var roundedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return roundedImage
}
uiimageview.layer.cornerRadius = uiimageview.frame.size.height/2;
uiimageview.clipToBounds = YES;
#import <QuartzCore/QuartzCore.h>
// UIImageView+OSExt.h
#import <UIKit/UIKit.h>
#interface UIImageView (OSExt)
- (void)setBorder:(CGFloat)borderWidth color:(UIColor*)color;
#end
// UIImageView+OSExt.m
#import "UIImageView+OSExt.h"
#implementation UIImageView (OSExt)
- (void)layoutSublayersOfLayer:(CALayer *)layer
{
for ( CALayer *sub in layer.sublayers )
{
if ( YES == [sub.name isEqual:#"border-shape"])
{
CGFloat borderHalf = floor([(CAShapeLayer*)sub lineWidth] * .5);
sub.frame = layer.bounds;
[sub setBounds:CGRectInset(layer.bounds, borderHalf, borderHalf)];
[sub setPosition:CGPointMake(CGRectGetMidX(layer.bounds),
CGRectGetMidY(layer.bounds))];
}
}
}
- (void)setBorder:(CGFloat)borderWidth color:(UIColor*)color
{
assert(self.frame.size.width == self.frame.size.height);
for ( CALayer *sub in [NSArray arrayWithArray:self.layer.sublayers] )
{
if ( YES == [sub.name isEqual:#"border-shape"])
{
[sub removeFromSuperlayer];
break;
}
}
CGFloat borderHalf = floor(borderWidth * .5);
self.layer.cornerRadius = self.layer.bounds.size.width * .5;
CAShapeLayer *circleLayer = [CAShapeLayer layer];
self.layer.delegate = (id<CALayerDelegate>)self;
circleLayer.name = #"border-shape";
[circleLayer setBounds:CGRectInset(self.bounds, borderHalf, borderHalf)];
[circleLayer setPosition:CGPointMake(CGRectGetMidX(self.layer.bounds),
CGRectGetMidY(self.layer.bounds))];
[circleLayer setPath:[[UIBezierPath bezierPathWithOvalInRect:circleLayer.bounds] CGPath]];
[circleLayer setStrokeColor:color.CGColor];
[circleLayer setFillColor:[UIColor clearColor].CGColor];
[circleLayer setLineWidth:borderWidth];
{
circleLayer.shadowOffset = CGSizeZero;
circleLayer.shadowColor = [[UIColor whiteColor] CGColor];
circleLayer.shadowRadius = borderWidth;
circleLayer.shadowOpacity = .9f;
circleLayer.shadowOffset = CGSizeZero;
}
// Add the sublayer to the image view's layer tree
[self.layer addSublayer:circleLayer];
// old variant
//CALayer *layer = self.layer;
//layer.masksToBounds = YES;
//layer.cornerRadius = self.frame.size.width * 0.5;
//layer.borderWidth = borderWidth;
//layer.borderColor = color;
}
#end
Setting cornerRadius and clipsToBounds is the right way to do this. However if the view's size changes, the radius will not update. In order to get proper resizing and animation behavior, you need to create a UIImageView subclass.
class RoundImageView: UIImageView {
override var bounds: CGRect {
get {
return super.bounds
}
set {
super.bounds = newValue
setNeedsLayout()
}
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.width / 2.0
clipsToBounds = true
}
}
Try this to get rounded corners of the image View and also to colour the corners:
imageView.layer.cornerRadius = imageView.frame.size.height/2;
imageView.layer.masksToBounds = YES;
imageView.layer.borderColor = [UIColor colorWithRed:148/255. green:79/255. blue:216/255. alpha:1.0].CGColor;
imageView.layer.borderWidth=2;
Condition*: The height and the width of the imageView must be same to get rounded corners.
layer.cornerRadius = imageviewHeight/2
layer.masksToBounds = true
It is possible but I'll advice you to create transparent png image (mask) with round corners and place it over you image with UIImageView. It might be quicker solution (for example if you need animations or scrolling).
Here how i set my rounded avatar at the center of it contain view:
-(void)setRoundedAvatar:(UIImageView *)avatarView toDiameter:(float)newSize atView:(UIView *)containedView;
{
avatarView.layer.cornerRadius = newSize/2;
avatarView.clipsToBounds = YES;
avatarView.frame = CGRectMake(0, 0, newSize, newSize);
CGPoint centerValue = CGPointMake(containView.frame.size.width/2, containedView.frame.size.height/2);
avatarView.center = centerValue;
}
Circle with UIBeizerPath #Swift-3 && #imageExtension
class ViewController: UIViewController {
#IBOutlet weak var imageOutlet: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let image = UIImage(named: "IMG_0001.JPG")
if let image = image {
let renderimage = image.imageCroppingBezierPath(path: UIBezierPath(arcCenter: CGPoint(x:image.size.width/2,y:image.size.width/2 ) , radius: 200, startAngle: 0, endAngle: (2 * CGFloat(M_PI) ), clockwise: true) )
imageOutlet.image = renderimage
}
}
}
extension UIImage {
func imageCroppingBezierPath(path:UIBezierPath) ->UIImage {
let frame = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
//Defining a graphic context to paint on
UIGraphicsBeginImageContextWithOptions(self.size, false, 0.0)
//Get the current graphics context (if it exists)
let context = UIGraphicsGetCurrentContext()
//save the current graphic context
context?.saveGState()
// clipping area
path.addClip()
self.draw(in: frame)
//To extract an image from our canvas
let image = UIGraphicsGetImageFromCurrentImageContext()
//restore graphic context
context?.restoreGState()
//remove current context from stack
UIGraphicsEndImageContext()
return image!
}
}
# import QuartzCore framework
imageView.layer.cornerRadius=imgvwUser.frame.size.width/2;
imageView.layer.masksToBounds = YES;
The height and the width of the imageView must be same to get rounded corners.