I am trying to create a resizable version of the following image (40x28, the small triangle at the bottom should be centered horizontally):
UIEdgeInsets imageInsets = UIEdgeInsetsMake(4.0f, 4.0f, 8.0f, 4.0f);
UIImage *pinImage = [[UIImage imageNamed:#"map_pin"] resizableImageWithCapInsets:imageInsets];
As per the documentation, this simple approach won't work since my triangle falls into resizable area (fixed height but scalable width):
This will cause the same problem as described in this very similar SO post - duplicated (tiled) triangles. But unfortunately their solution doesn't work for my particular case since excluding the triangle from scalable area causes its misplacement and it won't be centered horizontally due to obvious reasons.
So I wonder... is it possible to achieve my goal using the [UIImage resizableImageWithCapInsets:] method at all? Or may be I should try something else like drawing everything in the code instead?
Basically, I would like to implement something like Info Windows from Google Maps SDK:
You can achieve that by using UIBezierPath.The following code in swift but you should be able to achieve the same thing in Objective-C
Instead of starting the code with a straight line :
path.moveToPoint(CGPoint(x: 300, y: 0))
I instead start with an arc (upper right):
path.addArcWithCenter(CGPoint(x: 300-10, y: 50), radius: 10 , startAngle: 0 , endAngle: CGFloat(M_PI/2) , clockwise: true) //1st rounded corner
and by doing this, I have four rounded corners and I just need to add a straight line at the end of the code right before:
path.closePath()
Here is the code and a screenshot:
let path = UIBezierPath()
path.addArcWithCenter(CGPoint(x: 300-10, y: 50), radius: 10 , startAngle: 0 , endAngle: CGFloat(M_PI/2) , clockwise: true) //1st rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 50), radius:10, startAngle: CGFloat(2 * M_PI / 3), endAngle:CGFloat(M_PI) , clockwise: true)// 2rd rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 10), radius:10, startAngle: CGFloat(M_PI), endAngle:CGFloat(3 * M_PI / 2), clockwise: true)// 3rd rounded corner
// little triangle
path.addLineToPoint(CGPoint(x:240 , y:0))
path.addLineToPoint(CGPoint(x: 245, y: -10))
path.addLineToPoint(CGPoint(x:250, y: 0))
path.addArcWithCenter(CGPoint(x: 290, y: 10), radius: 10, startAngle: CGFloat(3 * M_PI / 2), endAngle: CGFloat(2 * M_PI ), clockwise: true)
path.addLineToPoint(CGPoint(x:300 , y:50))
path.closePath()
I decided to go with UIBezierPath as Assam Al-Zookery suggested. Here is the complete code in Objective-C (should be reusable and flexible enough):
#implementation TestView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.clipsToBounds = YES;
self.opaque = NO;
}
return self;
}
- (void)drawRect:(CGRect)rect {
UIBezierPath *path = [UIBezierPath bezierPath];
path.lineWidth = 2.0f;
path.lineJoinStyle = kCGLineJoinRound;
[[UIColor blackColor] setStroke];
[[UIColor redColor] setFill];
CGFloat arcRadius = 5.0f;
CGFloat padding = 5.0f;
CGFloat triangleSide = 20.0f;
CGFloat triangleHeight = ((triangleSide * sqrtf(3.0f)) / 2);
CGFloat rectHeight = CGRectGetHeight(rect) - triangleHeight - padding - (arcRadius * 2);
// Top left corner
[path addArcWithCenter:CGPointMake(arcRadius + padding, arcRadius + padding)
radius:arcRadius
startAngle:DEGREES_TO_RADIANS(180)
endAngle:DEGREES_TO_RADIANS(270)
clockwise:YES];
// Top edge
[path addLineToPoint:CGPointMake(CGRectGetWidth(rect) - arcRadius - padding, path.currentPoint.y)];
// Top right corner
[path addArcWithCenter:CGPointMake(path.currentPoint.x, path.currentPoint.y + arcRadius)
radius:arcRadius
startAngle:DEGREES_TO_RADIANS(270)
endAngle:DEGREES_TO_RADIANS(0)
clockwise:YES];
// Right edge
[path addLineToPoint:CGPointMake(CGRectGetWidth(rect) - padding, rectHeight + padding)];
// Bottom right corner
[path addArcWithCenter:CGPointMake(path.currentPoint.x - arcRadius, path.currentPoint.y)
radius:arcRadius
startAngle:DEGREES_TO_RADIANS(0)
endAngle:DEGREES_TO_RADIANS(90)
clockwise:YES];
// Bottom edge (right part)
[path addLineToPoint:CGPointMake((CGRectGetWidth(rect) / 2) + (triangleSide / 2), path.currentPoint.y)];
// Triangle
[path addLineToPoint:CGPointMake(path.currentPoint.x - (triangleSide / 2), path.currentPoint.y + triangleHeight)];
[path addLineToPoint:CGPointMake(path.currentPoint.x - (triangleSide / 2), path.currentPoint.y - triangleHeight)];
// Bottom edge (left part)
[path addLineToPoint:CGPointMake(padding + arcRadius, path.currentPoint.y)];
// Bottom left corner
[path addArcWithCenter:CGPointMake(path.currentPoint.x, path.currentPoint.y - arcRadius)
radius:arcRadius
startAngle:DEGREES_TO_RADIANS(90)
endAngle:DEGREES_TO_RADIANS(180)
clockwise:YES];
[path closePath];
[path stroke];
[path fill];
}
#end
Related
I have a view in which I want to draw a circle with UIBezierPath bezierPathWithArcCenter. My view's initWithFrame is as follows:
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self == nil) {
return nil;
}
self.circleLayer = [CAShapeLayer layer];
self.circleLayer.fillColor = UIColor.clearColor.CGColor;
self.circleLayer.strokeColor = UIColor.blackColor.CGColor;
self.circleLayer.lineWidth = 4;
self.circleLayer.bounds = self.bounds;
self.startAngle = 0.0;
self.endAngle = M_PI * 2;
CGPoint center = CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5);
CGFloat radius = self.frame.size.height * 0.45;
self.circleLayer.path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:self.startAngle endAngle:self.endAngle clockwise:YES].CGPath;
[self.layer addSublayer:self.circleLayer];
self.backgroundColor = UIColor.greenColor;
return self;
}
I expected that would produce a black circle centered in my view. However, the actual output looks like this:
What am I doing wrong?
EDIT
The weirdest part is that it's drawn correctly after a fresh install of the app - any consecutive launches result in it being drawn like in the image attached. Why?
Do not use view's center!
In my case, using ,
let centerPoint = CGPoint(x: bounds.midX, y: bounds.midY)
for center point solved my problem.
The reason for this seems to be a bug in iOS. The CAShapeLayer has its bounds set properly
self.circleLayer.bounds = self.bounds;
Upon inspecting it, the origin and size of the layer is fine, but only after first launch of the app after installing. Any subsequent launch of the app will result in its size being (0, 0). Removing the app and installing it again will result in the circle being drawn properly once.
I fixed it by setting the layer's frame again further down the road.
I ran into somewhat same problem, but for me the path was not drawing in center of my view even on the first launch.
This was my code at the time of problem:
let circularPath = UIBezierPath(arcCenter: CGPoint(x: containerView.frame.size.width/2, y: containerView.frame.size.height/2), radius: 50, startAngle: (-CGFloat.pi / 2), endAngle: (CGFloat.pi * 2), clockwise: true)
the 'containerView' is the view containing the circle drawn by bazierpath.
but then I did the following on a hunch and it worked.
let centerPoint = CGPoint(x: containerView.frame.size.width/2, y: containerView.frame.size.height/2)
let circularPath = UIBezierPath(arcCenter: centerPoint, radius: 50, startAngle: (-CGFloat.pi / 2), endAngle: (CGFloat.pi * 2), clockwise: true)
Basically I had to create the center point before assigning it to the bazierpath. Hope this helps someone.
I am trying to implement Bar code Scanner to my iPhone App using AVFoundation in Objective C. Bar code scanning part is working fine, but the issue is currently preview is displaying in the whole screen of the device and I want scanner preview to be display in a specific area of the device and need to change brightness area of the background (please refer to the below screenshot).
How can I place specific region of the AVCaptureVideoPreviewLayer to get the Bar scanner result with the corners?
I have one example in my pj, it's in Swift but I guess you can convert it fine, tweak the code as you needed, 55 is the length of each line
To create the corner, maybe this will do, scanRectView is the interest rect:
func createFrame() -> CAShapeLayer {
let height: CGFloat = self.scanRectView.frame.size.height
let width: CGFloat = self.scanRectView.frame.size.width
let path = UIBezierPath()
path.move(to: CGPoint(x: 5, y: 50))
path.addLine(to: CGPoint(x: 5, y: 5))
path.addLine(to: CGPoint(x: 50, y: 5))
path.move(to: CGPoint(x: height - 55, y: 5))
path.addLine(to: CGPoint(x: height - 5, y: 5))
path.addLine(to: CGPoint(x: height - 5, y: 55))
path.move(to: CGPoint(x: 5, y: width - 55))
path.addLine(to: CGPoint(x: 5, y: width - 5))
path.addLine(to: CGPoint(x: 55, y: width - 5))
path.move(to: CGPoint(x: width - 55, y: height - 5))
path.addLine(to: CGPoint(x: width - 5, y: height - 5))
path.addLine(to: CGPoint(x: width - 5, y: height - 55))
let shape = CAShapeLayer()
shape.path = path.cgPath
shape.strokeColor = UIColor.white.cgColor
shape.lineWidth = 5
shape.fillColor = UIColor.clear.cgColor
return shape
}
Objective C code :
CGFloat height = baseScannerView.frame.size.height+4;
CGFloat width = baseScannerView.frame.size.width+4;
UIBezierPath* path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(1, 25)];
[path addLineToPoint:CGPointMake(1, 1)];
[path addLineToPoint:CGPointMake(25, 1)];
[path moveToPoint:CGPointMake(width - 30, 1)];
[path addLineToPoint:CGPointMake(width - 5, 1)];
[path addLineToPoint:CGPointMake(width - 5, 25)];
[path moveToPoint:CGPointMake(1, height - 30)];
[path addLineToPoint:CGPointMake(1, height - 5)];
[path addLineToPoint:CGPointMake(25, height - 5)];
[path moveToPoint:CGPointMake(width - 30, height - 5)];
[path addLineToPoint:CGPointMake(width - 5, height - 5)];
[path addLineToPoint:CGPointMake(width - 5, height - 30)];
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = path.CGPath;
pathLayer.strokeColor = [[UIColor redColor] CGColor];
pathLayer.lineWidth = 5.0f;
pathLayer.fillColor = nil;
[self.scanRectView.layer addSublayer:pathLayer];
After create, add it like self.scanRectView.layer.addSublayer(self.createFrame()) in viewWillAppear or viewDidLayoutSubView
Result:
You can change AVCaptureVideoPreviewLayer frame, according to ur needs, like this, this is example,
AVCaptureVideoPreviewLayer *_CaptureLayer;
AVCaptureSession *_Capturesession;
_Capturesession = [[AVCaptureSession alloc] init];
_CaptureLayer = [AVCaptureVideoPreviewLayer layerWithSession:
_Capturesession];
_CaptureLayer = CGRectMake(self.view.frame.origin.x,
self.view.frame.origin.y+100,
self.view.frame.size.width,
self.view.frame.size.height/2.5);
_CaptureLayer = AVLayerVideoGravityResizeAspectFill;
[self.view.layer addSublayer: _CaptureLayer];
I am using CoreGraphics to draw a rounded rectangle, I am familiar with the CG API ready made to draw the rounded rectangle, but I do not want to use it because the rectangle is not a fully-uniform rounded rectangle, part of it would be a rounded rectangle, the other parts would be a set of connected paths, for example, the top-left and top-right would be a rounded rectangle edges, however, the bottom edges are a group of connected Bezier paths.
My question is, if I want to draw the entire shape as Bezier path, how should I calculate the control points in addCurveToPoint for the corners? I know the radius and the coordinates of the points (based on radius too).
(UPDATE)
I have a sample code, I am trying to understand the math behind it:
UIBezierPath * rectangle = [UIBezierPath bezierPath];
[rectangle moveToPoint:CGPointMake(0, 8)];
[rectangle addCurveToPoint:CGPointMake(8.01, 0) controlPoint1:CGPointMake(0, 3.58) controlPoint2:CGPointMake(3.59, 0)];
[rectangle addLineToPoint:CGPointMake(208, 0)];
[rectangle addCurveToPoint:CGPointMake(224, 16.01) controlPoint1:CGPointMake(216.84, 0) controlPoint2:CGPointMake(224, 7.16)];
[rectangle addLineToPoint:CGPointMake(224, 175)];
[rectangle addCurveToPoint:CGPointMake(192, 207) controlPoint1:CGPointMake(224, 192.67) controlPoint2:CGPointMake(209.67, 207)];
[rectangle addLineToPoint:CGPointMake(64, 207)];
[rectangle addCurveToPoint:CGPointMake(0, 142.99) controlPoint1:CGPointMake(28.65, 207) controlPoint2:CGPointMake(0, 178.35)];
[rectangle addLineToPoint:CGPointMake(0, 8)];
[rectangle closePath];
The corners radius are 8, 16, 32 and 64 for top-left, top-right, bottom-right and bottom-left
Thanks
I assume you want to add a 90 degree arc for the rounded corner. Use addArc instead of addCurveToPoint.
In Swift 3:
var path = UIBezierPath()
...
let center = CGPoint(x: topLeft.x + width - radius, y: topLeft.y)
path.addArc(withCenter: center, radius: radius, startAngle: CGFloat.pi, endAngle: CGFloat.pi * CGFloat(1.5), clockwise: true)
Of course, your parameters will vary.
Update
Based on your code, it should look like this:
UIBezierPath * rectangle = [UIBezierPath bezierPath];
[rectangle moveToPoint:CGPointMake(0, 8)];
[rectangle addArcWithCenter:CGPointMake(8, 8) radius:8 startAngle:M_PI endAngle:M_PI*1.5 clockwise:YES];
[rectangle addLineToPoint:CGPointMake(208, 0)];
[rectangle addArcWithCenter:CGPointMake(208, 16) radius:16 startAngle:M_PI*1.5 endAngle:0 clockwise:YES];
[rectangle addLineToPoint:CGPointMake(224, 175)];
[rectangle addArcWithCenter:CGPointMake(208, 175) radius:32 startAngle:0 endAngle:M_PI*0.5 clockwise:YES];
[rectangle addLineToPoint:CGPointMake(64, 207)];
[rectangle addArcWithCenter:CGPointMake(64, 175) radius:64 startAngle:M_PI*0.5 endAngle:M_PI clockwise:YES];
[rectangle closePath];
Based on #Codo answer, this is the final solution:
UIBezierPath * rectangle = [UIBezierPath bezierPath];
[rectangle moveToPoint:CGPointMake(0, 8)];
[rectangle addArcWithCenter:CGPointMake(8, 8) radius:8 startAngle:M_PI endAngle:M_PI*1.5 clockwise:YES];
[rectangle addLineToPoint:CGPointMake(208, 0)];
[rectangle addArcWithCenter:CGPointMake(208, 16) radius:16 startAngle:M_PI*1.5 endAngle:0 clockwise:YES];
[rectangle addLineToPoint:CGPointMake(224, 175)];
[rectangle addArcWithCenter:CGPointMake(192, 175) radius:32 startAngle:0 endAngle:M_PI*0.5 clockwise:YES];
[rectangle addLineToPoint:CGPointMake(64, 207)];
[rectangle addArcWithCenter:CGPointMake(64, 143) radius:64 startAngle:M_PI*0.5 endAngle:M_PI clockwise:YES];
[rectangle addLineToPoint:CGPointMake(0, 8)];
[rectangle closePath];
I have a chart which is in the shape of an gauge which has multiple design elements to it (see attachment). Key parts I'm struggling with is really getting a decent arc shape with dashed lines.
So far I'm not sure if I should be going down Core Graphics route or using something within UIKit, i.e. UIBezierPath.
I've tried this within a class that extend UIView which gives me the dashed lines but the arc itself isn't quite good enough:
class Example: UIView {
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 10.0)
CGContextSetStrokeColorWithColor(context, UIColor.greenColor().CGColor)
let dashArray:[CGFloat] = [1,10, 0, 0]
CGContextSetLineDash(context, 2, dashArray, 4)
CGContextMoveToPoint(context, 10, 200)
CGContextAddQuadCurveToPoint(context, 0, 0, 100, 200)
CGContextStrokePath(context)
}
}
There are then some other ways to get this going using UIBezierPath but I'm not sure how I would go about applying the dashed lines through here...
The main base of getting the arc with dashed lines is my main goal atm - I'm sure once I get this I'll be able to workout the gradient and animation :)
Any help would be appreciated :)
What you need is two bezier paths with different dash widths.
You can start from herE:
T0 get the higher dash bezier:
UIBezierPath* oval2Path = [UIBezierPath bezierPathWithOvalInRect: yourRect];
[UIColor.redColor setStroke];
oval2Path.lineWidth = 13;
CGFloat oval2Pattern[] = {2, 20};
[oval2Path setLineDash: oval2Pattern count: 2 phase: 0];
[oval2Path stroke];
and to get small dash patterned bezier, you need to reduce the gap between dashes:
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: yourRect];
[UIColor.redColor setStroke];
ovalPath.lineWidth = 6;
CGFloat ovalPattern[] = {2, 1};
[ovalPath setLineDash: ovalPattern count: 2 phase: 0];
[ovalPath stroke];
and now you can put these two bezier path together:
- (void)drawFrame: (CGRect)frame
{
// Oval Drawing
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(CGRectGetMinX(frame), CGRectGetMinY(frame), 70, 70)];
[UIColor.redColor setStroke];
ovalPath.lineWidth = 6;
CGFloat ovalPattern[] = {2, 1};
[ovalPath setLineDash: ovalPattern count: 2 phase: 0];
[ovalPath stroke];
// Oval 2 Drawing
UIBezierPath* oval2Path = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(CGRectGetMinX(frame) + 0.5, CGRectGetMinY(frame) - 0.5, 70, 70)];
[UIColor.redColor setStroke];
oval2Path.lineWidth = 13;
CGFloat oval2Pattern[] = {2, 20};
[oval2Path setLineDash: oval2Pattern count: 2 phase: 0];
[oval2Path stroke];
}
Swift:
func drawCanvas1(frame frame: CGRect = CGRect(x: 86, y: 26, width: 70, height: 70)) {
let context = UIGraphicsGetCurrentContext()
// Oval Drawing
let ovalPath = UIBezierPath(ovalInRect: CGRect(x: frame.minX, y: frame.minY, width: 70, height: 70))
UIColor.redColor().setStroke()
ovalPath.lineWidth = 6
CGContextSaveGState(context)
CGContextSetLineDash(context, 4.5, [0, 1], 2)
ovalPath.stroke()
CGContextRestoreGState(context)
// Oval 2 Drawing
let oval2Path = UIBezierPath(ovalInRect: CGRect(x: frame.minX + 0.5, y: frame.minY - 0.5, width: 70, height: 70))
UIColor.redColor().setStroke()
oval2Path.lineWidth = 13
CGContextSaveGState(context)
CGContextSetLineDash(context, 39, [1, 10], 2)
oval2Path.stroke()
CGContextRestoreGState(context)
}
Similarly you can follow the same approach for arcs, where you just need to replace the bezierPathWithOval method with bezierPathWithArcCenter method
Please note that:
CGFloat ovalPattern[] = {2, 1}; //2 is Dash width and 1 is gap between dashes
You can adjust these values for accuracy!
This is currently the best I could do to translate this over to a UIBezierPath. As I find better ways to do this, I'll update my code.
// First Arc //
guageArcOne.path = UIBezierPath(arcCenter: centerPoint, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true).CGPath
guageArcOne.fillColor = UIColor.clearColor().CGColor
guageArcOne.strokeColor = UIColor.greenColor().CGColor
guageArcOne.lineWidth = 10.0
guageArcOne.strokeEnd = 1.0
guageArcOne.lineDashPattern = [1,10, 0, 0]
guageArcOne.lineDashPhase = 2.0
arcContainerView.layer.addSublayer(guageArcOne)
// Second Arc //
guageArcTwo.path = UIBezierPath(arcCenter: centerPoint, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true).CGPath
guageArcTwo.fillColor = UIColor.clearColor().CGColor
guageArcTwo.strokeColor = UIColor.greenColor().CGColor
guageArcTwo.lineWidth = 10.0
guageArcTwo.strokeEnd = 1.0
guageArcTwo.lineDashPattern = [1,2, 0, 0]
guageArcTwo.lineDashPhase = 2.0
arcContainerView.layer.addSublayer(guageArcTwo)
EDIT: Added a second arc for the shorter, more frequent dashes.
I try to change a circle into a square and vice versa and I am almost there. However it doesn't animates as expected. I would like all corners of a square to be animated/morphed at the same time but what I get is the following:
I use CAShapeLayer and CABasicAnimation in order to animate shape property.
Here is how I create the circle path:
- (UIBezierPath *)circlePathWithCenter:(CGPoint)center radius:(CGFloat)radius
{
UIBezierPath *circlePath = [UIBezierPath bezierPath];
[circlePath addArcWithCenter:center radius:radius startAngle:0 endAngle:M_PI/2 clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:M_PI/2 endAngle:M_PI clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:M_PI endAngle:3*M_PI/2 clockwise:YES];
[circlePath addArcWithCenter:center radius:radius startAngle:3*M_PI/2 endAngle:M_PI clockwise:YES];
[circlePath closePath];
return circlePath;
}
Here is the square path:
- (UIBezierPath *)squarePathWithCenter:(CGPoint)center size:(CGFloat)size
{
CGFloat startX = center.x-size/2;
CGFloat startY = center.y-size/2;
UIBezierPath *squarePath = [UIBezierPath bezierPath];
[squarePath moveToPoint:CGPointMake(startX, startY)];
[squarePath addLineToPoint:CGPointMake(startX+size, startY)];
[squarePath addLineToPoint:CGPointMake(startX+size, startY+size)];
[squarePath addLineToPoint:CGPointMake(startX, startY+size)];
[squarePath closePath];
return squarePath;
}
I apply the circle path to one of my View's layers, set the fill etc. It draws perfectly.
Then in my gestureRecognizer selector I create and run the following animation:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.duration = 1;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = (__bridge id)(self.stateLayer.path);
animation.toValue = (__bridge id)(self.stopPath.CGPath);
self.stateLayer.path = self.stopPath.CGPath;
[self.stateLayer addAnimation:animation forKey:#"animatePath"];
As you can notice in the circlePathWithCenter:radius: and squarePathWithCenter:size: I follow the suggestion from here (to have the same number of segments and control points):
Smooth shape shift animation
The animation looks better then in the post from above but it is still not the one I want to achieve :(
I know that I can do it with simple CALayer and then set appropriate level of cornerRadius to make a circle out of square/rectangle and after that animate cornerRadius property to change it from circle to square but I'm still extremaly curious if it is even possible to do it with CAShapeLayer and path animation.
Thanks in advance for help!
After playing with it for a little while in the playground I noticed that each arc adds two segments to the bezier path, not just one. Moreover, calling closePath adds two more. So to keep the number of segments consistent, I ended up with adding fake segments to my square path. The code is in Swift, but I think it doesn't matter.
func circlePathWithCenter(center: CGPoint, radius: CGFloat) -> UIBezierPath {
let circlePath = UIBezierPath()
circlePath.addArcWithCenter(center, radius: radius, startAngle: -CGFloat(M_PI), endAngle: -CGFloat(M_PI/2), clockwise: true)
circlePath.addArcWithCenter(center, radius: radius, startAngle: -CGFloat(M_PI/2), endAngle: 0, clockwise: true)
circlePath.addArcWithCenter(center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI/2), clockwise: true)
circlePath.addArcWithCenter(center, radius: radius, startAngle: CGFloat(M_PI/2), endAngle: CGFloat(M_PI), clockwise: true)
circlePath.closePath()
return circlePath
}
func squarePathWithCenter(center: CGPoint, side: CGFloat) -> UIBezierPath {
let squarePath = UIBezierPath()
let startX = center.x - side / 2
let startY = center.y - side / 2
squarePath.moveToPoint(CGPoint(x: startX, y: startY))
squarePath.addLineToPoint(squarePath.currentPoint)
squarePath.addLineToPoint(CGPoint(x: startX + side, y: startY))
squarePath.addLineToPoint(squarePath.currentPoint)
squarePath.addLineToPoint(CGPoint(x: startX + side, y: startY + side))
squarePath.addLineToPoint(squarePath.currentPoint)
squarePath.addLineToPoint(CGPoint(x: startX, y: startY + side))
squarePath.addLineToPoint(squarePath.currentPoint)
squarePath.closePath()
return squarePath
}
I know this is an old question but this might help someone.
I think its better to animate corner radius to get this effect.
let v1 = UIView(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
v1.backgroundColor = UIColor.green
view.addSubview(v1)
animation:
let animation = CABasicAnimation(keyPath: "cornerRadius")
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
animation.fromValue = v1.layer.cornerRadius
animation.toValue = v1.bounds.width/2
animation.duration = 3
v1.layer.add(animation, forKey: "cornerRadius")
Based on #Danchoys answer, here it is for Objective-C.
info is a button of 60px and infoVC is the next ViewController being pushed:
UIBezierPath * maskPath = [UIBezierPath new];
[maskPath addArcWithCenter:info.center radius:15.0f startAngle:DEGREES_TO_RADIANS(180) endAngle:DEGREES_TO_RADIANS(270) clockwise:true];
[maskPath addArcWithCenter:info.center radius:15.0f startAngle:DEGREES_TO_RADIANS(270) endAngle:DEGREES_TO_RADIANS(0) clockwise:true];
[maskPath addArcWithCenter:info.center radius:15.0f startAngle:DEGREES_TO_RADIANS(0) endAngle:DEGREES_TO_RADIANS(90) clockwise:true];
[maskPath addArcWithCenter:info.center radius:15.0f startAngle:DEGREES_TO_RADIANS(90) endAngle:DEGREES_TO_RADIANS(180) clockwise:true];
[maskPath closePath];
UIBezierPath * endPath = [UIBezierPath new];
[endPath moveToPoint:CGPointMake(0,0)];
[endPath addLineToPoint:endPath.currentPoint];
[endPath addLineToPoint:CGPointMake(w, 0)];
[endPath addLineToPoint:endPath.currentPoint];
[endPath addLineToPoint:CGPointMake(w, h)];
[endPath addLineToPoint:endPath.currentPoint];
[endPath addLineToPoint:CGPointMake(0, h)];
[endPath addLineToPoint:endPath.currentPoint];
[endPath closePath];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = info.bounds;
maskLayer.path = maskPath.CGPath;
_infoVC.view.layer.mask = maskLayer;
CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.fromValue = (id)maskPath.CGPath;
animation.toValue = (id)endPath.CGPath;
animation.duration = 0.3f;
[maskLayer addAnimation:animation forKey:nil];
[maskLayer setPath:endPath.CGPath];
I can highly recommend the library CanvasPod, it also has a predefined "morph" animation and is easy to integrate. You can also check out examples on the website canvaspod.io.
This is handy for people making a recording button and want a native iOS like animation.
Instantiate this class within the UIViewController you would like to have the button. Add a UITapGestureRecogniser and call animateRecordingButton when tapped.
import UIKit
class RecordButtonView: UIView {
enum RecordingState {
case ready
case recording
}
var currentState: RecordingState = .ready
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = true
self.backgroundColor = .white
// My button is 75 points in height and width so this
// Will make a circle
self.layer.cornerRadius = 35
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func animateRecordingButton() {
var newRadius: CGFloat
var scale: CGFloat
switch currentState {
case .ready:
// The square radius
newRadius = 10
// Lets scale it down to 70%
scale = 0.7
currentState = .recording
case .recording:
// Animate back to cirlce
newRadius = 35
currentState = .ready
// Animate back to full scale
scale = 1
}
UIView.animate(withDuration: 1) {
self.layer.cornerRadius = newRadius
self.transform = CGAffineTransform(scaleX: scale, y: scale)
}
}
}