start and end angle of UIBezierPath? - ios

I have coded as below for half circle in iOS using UIBezierPath and CAShapeLayer.
clockWiseLayer = [[CAShapeLayer alloc] init];
CGFloat startAngle = -M_PI_2;
CGFloat endAngle = M_PI + M_PI_2;
CGFloat width = CGRectGetWidth(imageView.frame)/2.0f + 30;
CGFloat height = CGRectGetHeight(imageView.frame)/2.0f +50;
CGPoint centerPoint = CGPointMake(width, height);
float radius = CGRectGetWidth(imageView.frame)/2+10;
clockWiseLayer.path = [UIBezierPath bezierPathWithArcCenter:centerPoint
radius:radius
startAngle:startAngle
endAngle:endAngle
clockwise:YES].CGPath;
clockWiseLayer.fillColor = [UIColor clearColor].CGColor;
clockWiseLayer.strokeColor = [UIColor blueColor].CGColor;
clockWiseLayer.borderColor = [UIColor greenColor].CGColor;
clockWiseLayer.backgroundColor = [UIColor redColor].CGColor;
clockWiseLayer.strokeStart = 0.0f;
clockWiseLayer.strokeEnd = 0.5f;
clockWiseLayer.lineWidth = 2.0f;
clockWiseLayer.borderWidth = 5.0f;
clockWiseLayer.shouldRasterize = NO;
[self.layer addSublayer:clockWiseLayer];
This results as below.
I want this blue half circle on the opposite side of the World Globe.
It is half Circle, but I want it on the other side, also CounterClockWise.
I am unable to set start and end angle for that, while clockwise:NO.
Thanks.

Check documentation for coordinates:
http://i.stack.imgur.com/1yJo6.png
Y-Axis is reversed compared to standard coordinate system in mathematics.
You should draw from 1/2*PI (bottom anchor) to 3/2*PI (top anchor) and then you set strokeStart to 0.0f and strokeEnd to 1.0f (so it fills whole path).
Working code using iOS constants:
CGFloat startAngle = M_PI_2;
CGFloat endAngle = startAngle + M_PI;
CGFloat width = CGRectGetWidth(imageView.frame)/2.0f;
CGFloat height = CGRectGetHeight(imageView.frame)/2.0f;
CGPoint centerPoint = CGPointMake(width, height);
float radius = CGRectGetWidth(imageView.frame)/2+10;
CAShapeLayer* clockWiseLayer = [[CAShapeLayer alloc] init];
clockWiseLayer.path = [UIBezierPath bezierPathWithArcCenter:centerPoint
radius:radius
startAngle:startAngle
endAngle:endAngle
clockwise:YES].CGPath;
clockWiseLayer.fillColor = [UIColor clearColor].CGColor;
clockWiseLayer.strokeColor = [UIColor blueColor].CGColor;
clockWiseLayer.borderColor = [UIColor greenColor].CGColor;
clockWiseLayer.backgroundColor = [UIColor redColor].CGColor;
clockWiseLayer.strokeStart = 0.0f;
clockWiseLayer.strokeEnd = 1.0f;
clockWiseLayer.lineWidth = 2.0f;
clockWiseLayer.borderWidth = 5.0f;
[self.layer addSublayer:clockWiseLayer];

You start from top of the circle with -M_PI_2 and end at M_PI + M_PI_2 (if you want to have full circle and limit it using strokeEnd, strokeStart). Then set the circle path to draw from half of the end of the path (left side of image) instead from beginning to half (right side of the image)
CGFloat startAngle = -M_PI_2;
CGFloat endAngle = M_PI + M_PI_2;
clockWiseLayer.strokeStart = .5f;
clockWiseLayer.strokeEnd = 1.0f;

I have played with bezierPathWithArcCenter and it works quite strange if clockwise = NO. But if you want to create an circle bezier path with conterclockwise direction you can create a clockwise path and then revert it with bezierPathByReversingPath
A new path object with the same path shape but for which the path has been created in the reverse direction.

Related

How to fill a bezier path with control points with gradient color

I need to fill my bezier path with a gradient color. But the effect is not what I expected, I know this is caused by the drawing direction.
How can I achieve the effect of the second picture?
my bezierpath code
CGFloat deltaX = lineChartPoint.x - previousLineChartPoint.x;
CGFloat controlPointX = previousLineChartPoint.x + (deltaX / 2);
CGPoint controlPoint1 = CGPointMake(controlPointX, previousLineChartPoint.y);
CGPoint controlPoint2 = CGPointMake(controlPointX, lineChartPoint.y);
[bezierPath addCurveToPoint:CGPointMake(lineChartPoint.x, lineChartPoint.y) controlPoint1:controlPoint1 controlPoint2:controlPoint2];
my gradientLayer code
self.gradientLayer = [CAGradientLayer layer];
self.gradientLayer.frame = CGRectMake(kVPadding, 0, CGRectGetWidth(self.bounds) - 2 * kVPadding, CGRectGetHeight(self.bounds));
self.gradientLayer.colors = #[(__bridge id)[UIColor colorWithHex:0x8362FC alpha:0.9].CGColor,(__bridge id)[UIColor colorWithHex:0x517DF7 alpha:0.1].CGColor];
self.gradientLayer.locations=#[#0.0,#1.0];
self.gradientLayer.startPoint = CGPointMake(0.0,0.0);
self.gradientLayer.endPoint = CGPointMake(0.0,1);
[self.layer addSublayer:self.gradientLayer];
self.gradientShapeLayer = [[CAShapeLayer alloc] init];
self.gradientLayer.mask = self.gradientShapeLayer;
self.gradientShapeLayer.path = bezierPath.CGPath;
#Dragonthoughts

How to animate the fill of a CAShapeLayer?

I have a circular CAShapeLayer that I'd like to animate the fill of. I have created my CAShapeLayer as
- (void)viewDidLoad {
[super viewDidLoad];
// Filled layer
CAShapeLayer *filledCircleShape = [CAShapeLayer new];
filledCircleShape.frame = CGRectMake(100, 100, 100, 100);
filledCircleShape.fillColor = [UIColor purpleColor].CGColor;
filledCircleShape.strokeColor = [UIColor grayColor].CGColor;
filledCircleShape.lineWidth = 2;
filledCircleShape.path = [self filledBezierPathWithRelativeFill:.75 inFrame:filledCircleShape.frame].CGPath;
[self.view.layer addSublayer:filledCircleShape];
self.filleShapeLayer = filledCircleShape;
}
- (UIBezierPath *)filledBezierPathWithRelativeFill:(CGFloat)relativeFill
{
UIBezierPath *filledCircleArc = [UIBezierPath bezierPath];
CGFloat arcAngle = 2 * acos(1 - 2 * relativeFill); // 2arccos ((r - 2rp) / r) == 2 arccos(1 - 2p)
CGFloat startAngle = (M_PI - arcAngle) / 2;
CGFloat endAngle = startAngle + arcAngle;
[filledCircleArc addArcWithCenter:self.boundsCenter radius:self.radius startAngle:startAngle endAngle:endAngle clockwise:YES];
return filledCircleArc;
}
This looks like this:
I have tried the following to make it animate a path change.
- (void)animate
{
UIBezierPath *toBezierPath = [self filledBezierPathWithRelativeFill:0.3 inFrame:CGRectMake(100, 100, 100, 100)];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"path"];
pathAnimation.duration = 5.f;
pathAnimation.toValue = (__bridge id)toBezierPath.CGPath;
[self.filleShapeLayer addAnimation:pathAnimation forKey:#"path"];
}
which kind of works. The shape layer animates but looks like this
I want the shape layer to animate like an hour glass, starting at one percentage and then animates down to another.
Any ideas of how I can achieve this using a CAShapeLayer?
The CAShapeLayer is overkill for the fill. It isn't magically going to animate the way you want, and it isn't necessary anyway. Just start with a big square purple view and animate it downwards — but put a mask so that only the interior of the shape region is visible — as I do here (except that I'm filling instead of emptying):

CGRect Intersects Rect better alternative

I'm trying to want to write an method whenever two UIImageViews intersect. The only way I know how to do it is CGRectIntersectsRect. But that only works with rectangles, but my images are circles. Isn't there a better alternative? Thank you!
You can do something like this:
CGPoint a = imageViewA.center;
CGPoint b = imageViewB.center;
CGFloat distance = sqrt(pow((b.x - a.x),2) + pow((b.y - a.y),2));
if(distance < (imageViewA.bounds.size.width/2.0 + imageViewB.bounds.size.width/2.0)){
//images are touching.
}
Assuming you know the radius of each circle (if the image is a square in size then it will be height/2 or width/2 if the circle entirely occupies fills the image) do the following for detecting collision between two circles:
Calculate the distance between the center point of the two circle:
distance = sqrt((p1.x - p2.x)^2 + (p1.y - p2.y)^2);
p1 - Center point of circle 1
p2 - Center point of circle 2
Calculate the sum of the radius of the two circles :
radiusSum = radius1 + radius2;
If distance <= radiusSum then you have a collision
It's use core animation.
CALayer *subLayer = [CALayer layer];
subLayer.backgroundColor = [UIColor blueColor].CGColor;
subLayer.shadowOffset = CGSizeMake(0, 3);
subLayer.shadowRadius = 100.0;
subLayer.shadowColor = [UIColor blackColor].CGColor;
subLayer.shadowOpacity = 0.8;
subLayer.frame = CGRectMake(30, 30, 128, 192);
subLayer.borderColor = [UIColor blackColor].CGColor;
subLayer.borderWidth = 2.0;
subLayer.cornerRadius = 10.0;
[self.view.layer addSublayer:subLayer];
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = subLayer.bounds;
imageLayer.cornerRadius = 48.0;// Here set you right size, then they looks like a circles
imageLayer.contents = (id)[UIImage imageNamed:#"141.png"].CGImage;
imageLayer.masksToBounds = YES;
[subLayer addSublayer:imageLayer];

Getting a wave like animation in concentric circles in ios

I have this need to display two points emitting signals. The way the signals is represented is the wifi signal strength indicator that we commonly see in Macs.
However, there are a couple of changes to that:
It is inverted. The source is pointed at top. Look at the image below:
And I need to animate the lines to give the indication that the source is emitting signal.
I was able to get the attached images by overriding the drawRect of the View class:
CGContext context = UIGraphics.GetCurrentContext();
context.SetLineWidth(2.0f);
UIColor color = UIColor.FromRGB(65, 137, 77);
context.SetStrokeColorWithColor(color.CGColor);
float maxRadius = rect.Height;
const float delta = 15;
float Y = center.Y;
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= delta) {
context.AddArc(center.X, Y, currentRadius, -ToRadians(startAngle), -ToRadians(endAngle), true);
context.StrokePath();
}
I'm out of my depths here.
If someone can point me to the right direction, that would be super awesome!
I took a shot at this using Core Animation. I just add a shape layer with a circle shape for each of the waves and then dynamically adjust their strokeStart and strokeEnd properties to get it to vary the width of each wave. So the output is like this:
Here is the initialization code:
- (void)addWavesToView
{
CGRect rect = CGRectMake(0.0f, 0.0f, 300.0f, 300.0f);
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:rect];
for (NSInteger i = 0; i < 10; ++i) {
CAShapeLayer *waveLayer = [CAShapeLayer layer];
waveLayer.bounds = rect;
waveLayer.position = CGPointMake(300.0f, 100.0f);
waveLayer.strokeColor = [[UIColor lightGrayColor] CGColor];
waveLayer.fillColor = [[UIColor clearColor] CGColor];
waveLayer.lineWidth = 5.0f;
waveLayer.path = circlePath.CGPath;
// Translate on the y axis to shift all the layers down while
// we're creating them. Could do this with the position value as well
// but this is a little cleaner.
waveLayer.transform = CATransform3DMakeTranslation(0.0f, i*25, 0.0f);
// strokeStart begins at 3:00. You would need to transform (rotate)
// the layer 90 deg CCW to have it start at 12:00
waveLayer.strokeStart = 0.25 - ((i+1) * 0.01f);
waveLayer.strokeEnd = 0.25 + ((i+1) * 0.01f);
[self.view.layer addSublayer:waveLayer];
}
}
And then here is where I add the animations for each layer:
- (void)addAnimationsToLayers
{
NSInteger timeOffset = 10;
for (CALayer *layer in self.view.layer.sublayers) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeColor"];
animation.duration = 10.0f;
// Stagger the animations so they don't begin until the
// desired time in each
animation.timeOffset = timeOffset--;
animation.toValue = (id)[[UIColor lightGrayColor] CGColor];
animation.fromValue = (id)[[UIColor darkGrayColor] CGColor];
// Repeat forever
animation.repeatCount = HUGE_VALF;
// Run it at 10x. You can adjust this to taste.
animation.speed = 10;
// Add to the layer to start the animation
[layer addAnimation:animation forKey:#"strokeColor"];
}
}
I posted it to github. Not sure if it's exactly what you're looking for, but hopefully it points you in the right direction.

Roof over square root symbol

I want to write a UILabel that can print square root expressions with a roof on it instead of just a simple √x. There should be a line over the x there as if it's written on paper.
Using Quartz 2D (the QuartzCore framework), you could just draw the line over it:
Thus, given
self.label.text = #"√23+45";
[self addSquareRootTopTo:self.label];
This would draw a line over the characters after the √ symbol:
- (void)addSquareRootTopTo:(UILabel *)label
{
NSRange range = [label.text rangeOfString:#"√"];
if (range.location == NSNotFound)
return;
NSString *stringThruSqrtSymbol = [label.text substringToIndex:range.location + 1];
NSString *stringAfterSqrtSymbol = [label.text substringFromIndex:range.location + 1];
CGSize sizeThruSqrtSymbol;
CGSize sizeAfterSqrtSymbol;
// get the size of the string given the label's font
if ([stringThruSqrtSymbol respondsToSelector:#selector(sizeWithAttributes:)])
{
// for iOS 7
NSDictionary *attributes = #{NSFontAttributeName : label.font};
sizeThruSqrtSymbol = [stringThruSqrtSymbol sizeWithAttributes:attributes];
sizeAfterSqrtSymbol = [stringAfterSqrtSymbol sizeWithAttributes:attributes];
}
else
{
// for earlier versions of iOS
sizeThruSqrtSymbol = [stringThruSqrtSymbol sizeWithFont:label.font];
sizeAfterSqrtSymbol = [stringAfterSqrtSymbol sizeWithFont:label.font];
}
// create path for line over the stuff after the square root sign
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(label.frame.origin.x + sizeThruSqrtSymbol.width, label.frame.origin.y)];
[path addLineToPoint:CGPointMake(label.frame.origin.x + sizeThruSqrtSymbol.width + sizeAfterSqrtSymbol.width, label.frame.origin.y)];
// now add that line to a CAShapeLayer
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = path.CGPath;
layer.lineWidth = 1.0;
layer.strokeColor = [[UIColor blackColor] CGColor];
layer.fillColor = [[UIColor clearColor] CGColor];
[self.view.layer addSublayer:layer];
}
That yields a less than satisfactory gap:
You could play around with this to nudge the top of the square root symbol down, but the alternative approach would be to use Quartz 2D to draw the entire square root symbol. Thus, take the square root symbol out of the label's text value:
self.label.text = #"23+45";
[self addSquareRootTo:self.label];
And then draw the whole square root symbol in Quartz 2D:
static CGFloat const part1 = 0.12; // tiny diagonal will be 12% of the height of the text frame
static CGFloat const part2 = 0.35; // medium sized diagonal will be 35% of the height of the text frame
- (void)addSquareRootTo:(UILabel *)label
{
CGSize size;
if ([label.text respondsToSelector:#selector(sizeWithAttributes:)])
{
NSDictionary *attributes = #{NSFontAttributeName : label.font};
size = [label.text sizeWithAttributes:attributes];
}
else
{
size = [label.text sizeWithFont:label.font];
}
UIBezierPath *path = [UIBezierPath bezierPath];
// it's going to seem strange, but it's probably easier to draw the square root size
// right to left, so let's start at the top right of the text frame
[path moveToPoint:CGPointMake(label.frame.origin.x + size.width, label.frame.origin.y)];
// move to the top left
CGPoint point = CGPointMake(label.frame.origin.x, label.frame.origin.y);
[path addLineToPoint:point];
// now draw the big diagonal line down to the bottom of the text frame (at 15 degrees)
point.y += size.height;
point.x -= sinf(15 * M_PI / 180) * size.height;
[path addLineToPoint:point];
// now draw the medium sized diagonal back up (at 30 degrees)
point.y -= size.height * part2;
point.x -= sinf(30 * M_PI / 180) * size.height * part2;
[path addLineToPoint:point];
// now draw the tiny diagonal back down (again, at 30 degrees)
point.y += size.height * part1;
point.x -= sinf(30 * M_PI / 180) * size.height * part1;
[path addLineToPoint:point];
// now add the whole path to our view
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = path.CGPath;
layer.lineWidth = 1.0;
layer.strokeColor = [[UIColor blackColor] CGColor];
layer.fillColor = [[UIColor clearColor] CGColor];
[self.view.layer addSublayer:layer];
}
That looks a little better:
Clearly, you can implement this any way you want (using either of the above, or probably better, subclassing a UIView to putting this, or its CoreGraphics equivalent, in drawRect), but it illustrates the idea of drawing the square root symbol yourself.
By the way, if using Xcode version prior 5, you'll have to manually add the QuartzCore.framework to your project and then include the appropriate header:
#import <QuartzCore/QuartzCore.h>

Resources