UIBezierPath bezierPathWithArcCenter not properly centered - ios

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.

Related

How to scan Bar code in a specific area of AVCaptureVideoPreviewLayer using Objective C in iOS?

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];

Complex image (bubble / pin) and [UIImage resizableImageWithCapInsets:]

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

Swift : How to create a UIView with transparent circle in the middle?

There are some answers for objective c but did not find any regarding swift.
I would like to create a dark view with transparent circle in middle so that user can see the subview and interact with it. How can I implement that using swift.
More precisely I'm looking for a result like whatsapp profile picture implementation. There is this transparent circle in the middle and the user can see the picture and scroll for instance.
Thanks for your help guys!
Cyril's answer translated to swift to prevent extra-typing:
func circularOverlayMask() -> UIImage {
let bounds = self.view.bounds
let width = bounds.size.width
let height = bounds.size.height
let diameter = width
let radius = diameter / 2
let center = CGPointMake(width / 2, height / 2)
// Create the image context
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)
// Create the bezier paths
let clipPath = UIBezierPath(rect: bounds)
let maskPath = UIBezierPath(ovalInRect: CGRectMake(center.x - radius, center.y - radius, diameter, diameter))
clipPath.appendPath(maskPath)
clipPath.usesEvenOddFillRule = true
clipPath.addClip()
UIColor(white: 0, alpha: 0.5).setFill()
clipPath.fill()
let finalImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()
return finalImage
}
Here is a method I use in my projects to create circular masks (this is not in Swift but easily translatable):
- (UIImage *)circularOverlayMask
{
// Constants
CGRect bounds = self.navigationController.view.bounds;
CGFloat width = bounds.size.width;
CGFloat height = bounds.size.height;
CGFloat diameter = width - (INNER_EDGE_INSETS * 2);
CGFloat radius = diameter / 2;
CGPoint center = CGPointMake(width / 2, height / 2);
// Create the image context
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
// Create the bezier paths
UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:bounds];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(center.x - radius, center.y - radius, diameter, diameter)];
[clipPath appendPath:maskPath];
clipPath.usesEvenOddFillRule = YES;
[clipPath addClip];
[[UIColor colorWithWhite:0 alpha:0.5f] setFill];
[clipPath fill];
UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return finalImage;
}
I basically create a subview that I add above the image scrollView, like this:
UIImageView *maskView = [[UIImageView alloc] initWithImage:[self overlayMask]];
maskView.userInteractionEnabled = NO;
[self.view insertSubview:maskView aboveSubview:_scrollView];
Hope that helps.
(Originally found in DZNPhotoPickerController)

Create a custom TableView like the reminder native iOS App [duplicate]

In my application - there are four buttons named as follows:
Top - left
Bottom - left
Top - right
Bottom - right
Above the buttons there is an image view (or a UIView).
Now, suppose a user taps on - top - left button. Above image / view should be rounded at that particular corner.
I am having some difficulty in applying rounded corners to the UIView.
Right now I am using the following code to apply the rounded corners to each view:
// imgVUserImg is a image view on IB.
imgVUserImg.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"any Url Here"];
CALayer *l = [imgVUserImg layer];
[l setMasksToBounds:YES];
[l setCornerRadius:5.0];
[l setBorderWidth:2.0];
[l setBorderColor:[[UIColor darkGrayColor] CGColor]];
Above code is applying the roundness to each of corners of supplied View. Instead I just wanted to apply roundness to selected corners like - top / top+left / bottom+right etc.
Is it possible? How?
Starting in iOS 3.2, you can use the functionality of UIBezierPaths to create an out-of-the-box rounded rect (where only corners you specify are rounded). You can then use this as the path of a CAShapeLayer, and use this as a mask for your view's layer:
// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds
byRoundingCorners:UIRectCornerTopLeft
cornerRadii:CGSizeMake(10.0, 10.0)];
// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageView.bounds;
maskLayer.path = maskPath.CGPath;
// Set the newly created shape layer as the mask for the image view's layer
imageView.layer.mask = maskLayer;
And that's it - no messing around manually defining shapes in Core Graphics, no creating masking images in Photoshop. The layer doesn't even need invalidating. Applying the rounded corner or changing to a new corner is as simple as defining a new UIBezierPath and using its CGPath as the mask layer's path. The corners parameter of the bezierPathWithRoundedRect:byRoundingCorners:cornerRadii: method is a bitmask, and so multiple corners can be rounded by ORing them together.
EDIT: Adding a shadow
If you're looking to add a shadow to this, a little more work is required.
Because "imageView.layer.mask = maskLayer" applies a mask, a shadow will not ordinarily show outside of it. The trick is to use a transparent view, and then add two sublayers (CALayers) to the view's layer: shadowLayer and roundedLayer. Both need to make use of the UIBezierPath. The image is added as the content of roundedLayer.
// Create a transparent view
UIView *theView = [[UIView alloc] initWithFrame:theFrame];
[theView setBackgroundColor:[UIColor clearColor]];
// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:theView.bounds
byRoundingCorners:UIRectCornerTopLeft
cornerRadii:CGSizeMake(10.0f, 10.0f)];
// Create the shadow layer
CAShapeLayer *shadowLayer = [CAShapeLayer layer];
[shadowLayer setFrame:theView.bounds];
[shadowLayer setMasksToBounds:NO];
[shadowLayer setShadowPath:maskPath.CGPath];
// ...
// Set the shadowColor, shadowOffset, shadowOpacity & shadowRadius as required
// ...
// Create the rounded layer, and mask it using the rounded mask layer
CALayer *roundedLayer = [CALayer layer];
[roundedLayer setFrame:theView.bounds];
[roundedLayer setContents:(id)theImage.CGImage];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
[maskLayer setFrame:theView.bounds];
[maskLayer setPath:maskPath.CGPath];
roundedLayer.mask = maskLayer;
// Add these two layers as sublayers to the view
[theView.layer addSublayer:shadowLayer];
[theView.layer addSublayer:roundedLayer];
I used the answer over at How do I create a round cornered UILabel on the iPhone? and the code from How is a rounded rect view with transparency done on iphone? to make this code.
Then I realized I'd answered the wrong question (gave a rounded UILabel instead of UIImage) so I used this code to change it:
http://discussions.apple.com/thread.jspa?threadID=1683876
Make an iPhone project with the View template. In the view controller, add this:
- (void)viewDidLoad
{
CGRect rect = CGRectMake(10, 10, 200, 100);
MyView *myView = [[MyView alloc] initWithFrame:rect];
[self.view addSubview:myView];
[super viewDidLoad];
}
MyView is just a UIImageView subclass:
#interface MyView : UIImageView
{
}
I'd never used graphics contexts before, but I managed to hobble together this code. It's missing the code for two of the corners. If you read the code, you can see how I implemented this (by deleting some of the CGContextAddArc calls, and deleting some of the radius values in the code. The code for all corners is there, so use that as a starting point and delete the parts that create corners you don't need. Note that you can make rectangles with 2 or 3 rounded corners too if you want.
The code's not perfect, but I'm sure you can tidy it up a little bit.
static void addRoundedRectToPath(CGContextRef context, CGRect rect, float radius, int roundedCornerPosition)
{
// all corners rounded
// CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);
// CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);
// CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius,
// radius, M_PI / 4, M_PI / 2, 1);
// CGContextAddLineToPoint(context, rect.origin.x + rect.size.width - radius,
// rect.origin.y + rect.size.height);
// CGContextAddArc(context, rect.origin.x + rect.size.width - radius,
// rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1);
// CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + radius);
// CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + radius,
// radius, 0.0f, -M_PI / 2, 1);
// CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);
// CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius,
// -M_PI / 2, M_PI, 1);
// top left
if (roundedCornerPosition == 1) {
CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);
CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);
CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius,
radius, M_PI / 4, M_PI / 2, 1);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width,
rect.origin.y + rect.size.height);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y);
}
// bottom left
if (roundedCornerPosition == 2) {
CGContextMoveToPoint(context, rect.origin.x, rect.origin.y);
CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width,
rect.origin.y + rect.size.height);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);
CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius,
-M_PI / 2, M_PI, 1);
}
// add the other corners here
CGContextClosePath(context);
CGContextRestoreGState(context);
}
-(UIImage *)setImage
{
UIImage *img = [UIImage imageNamed:#"my_image.png"];
int w = img.size.width;
int h = img.size.height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
CGContextBeginPath(context);
CGRect rect = CGRectMake(0, 0, w, h);
addRoundedRectToPath(context, rect, 50, 1);
CGContextClosePath(context);
CGContextClip(context);
CGContextDrawImage(context, rect, img.CGImage);
CGImageRef imageMasked = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
[img release];
return [UIImage imageWithCGImage:imageMasked];
}
alt text http://nevan.net/skitch/skitched-20100224-092237.png
Don't forget that you'll need to get the QuartzCore framework in there for this to work.
I have used this code in many places in my code and it works 100% correctly. You can change any corder by changed one property "byRoundingCorners:UIRectCornerBottomLeft"
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerBottomLeft cornerRadii:CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = view.bounds;
maskLayer.path = maskPath.CGPath;
view.layer.mask = maskLayer;
[maskLayer release];
In iOS 11, we can now round some corners only
let view = UIView()
view.clipsToBounds = true
view.layer.cornerRadius = 8
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
CALayer extension with Swift 3+ syntax
extension CALayer {
func round(roundedRect rect: CGRect, byRoundingCorners corners: UIRectCorner, cornerRadii: CGSize) -> Void {
let bp = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: cornerRadii)
let sl = CAShapeLayer()
sl.frame = self.bounds
sl.path = bp.cgPath
self.mask = sl
}
}
It can be used like:
let layer: CALayer = yourView.layer
layer.round(roundedRect: yourView.bounds, byRoundingCorners: [.bottomLeft, .topLeft], cornerRadii: CGSize(width: 5, height: 5))
Stuarts example for rounding specific corners works great. If you want to round multiple corners like top left and right this is how to do it
// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageview
byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight
cornerRadii:CGSizeMake(10.0, 10.0)];
// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageview.bounds;
maskLayer.path = maskPath.CGPath;
// Set the newly created shape layer as the mask for the image view's layer
imageview.layer.mask = maskLayer;
there is an easier and faster answer that may work depending on your needs and also works with shadows. you can set maskToBounds on the superlayer to true, and offset the child layers so that 2 of their corners are outside the superlayer bounds, effectively cutting the rounded corners on 2 sides away.
of course this only works when you want to have only 2 rounded corners on the same side and the content of the layer looks the same when you cut off a few pixels from one side. works great for having bar charts rounded only on the top side.
Thanks for sharing. Here I'd like to share the solution on swift 2.0 for further reference on this issue. (to conform the UIRectCorner's protocol)
let mp = UIBezierPath(roundedRect: cell.bounds, byRoundingCorners: [.bottomLeft, .TopLeft], cornerRadii: CGSize(width: 10, height: 10))
let ml = CAShapeLayer()
ml.frame = self.bounds
ml.path = mp.CGPath
self.layer.mask = ml
See this related question. You'll have to draw your own rectangle to a CGPath with some rounded corners, add the CGPath to your CGContext and then clip to it using CGContextClip.
You can also draw the rounded rect with alpha values to an image and then use that image to create a new layer which you set as your layer's mask property (see Apple's documentation).
Half a decade late, but I think the current way people do this isn't 100% right. Many people have had the issue that using the UIBezierPath + CAShapeLayer method interferes with Auto-layout, especially when it is set on the Storyboard. No answers go over this, so I decided to add my own.
There is a very easy way to circumvent that: Draw the rounded corners in the drawRect(rect: CGRect) function.
For example, if I wanted top rounded corners for a UIView, I'd subclass UIView and then use that subclass wherever appropriate.
import UIKit
class TopRoundedView: UIView {
override func drawRect(rect: CGRect) {
super.drawRect(rect)
var maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: UIRectCorner.TopLeft | UIRectCorner.TopRight, cornerRadii: CGSizeMake(5.0, 5.0))
var maskLayer = CAShapeLayer()
maskLayer.frame = self.bounds
maskLayer.path = maskPath.CGPath
self.layer.mask = maskLayer
}
}
This is the best way to conquer the issue and doesn't take any time at all to adapt to.
Rounding only some corners won't play nice with auto resizing or auto layout.
So another option is to use regular cornerRadius and hide the corners you don't want under another view or outside its superview bounds making sure it is set to clip its contents.
To add to to the answer and the addition, I created a simple, reusable UIView in Swift. Depending on your use case, you might want to make modifications (avoid creating objects on every layout etc.), but I wanted to keep it as simple as possible. The extension allows you to apply this to other view's (ex. UIImageView) easier if you do not like subclassing.
extension UIView {
func roundCorners(_ roundedCorners: UIRectCorner, toRadius radius: CGFloat) {
roundCorners(roundedCorners, toRadii: CGSize(width: radius, height: radius))
}
func roundCorners(_ roundedCorners: UIRectCorner, toRadii cornerRadii: CGSize) {
let maskBezierPath = UIBezierPath(
roundedRect: bounds,
byRoundingCorners: roundedCorners,
cornerRadii: cornerRadii)
let maskShapeLayer = CAShapeLayer()
maskShapeLayer.frame = bounds
maskShapeLayer.path = maskBezierPath.cgPath
layer.mask = maskShapeLayer
}
}
class RoundedCornerView: UIView {
var roundedCorners: UIRectCorner = UIRectCorner.allCorners
var roundedCornerRadii: CGSize = CGSize(width: 10.0, height: 10.0)
override func layoutSubviews() {
super.layoutSubviews()
roundCorners(roundedCorners, toRadii: roundedCornerRadii)
}
}
Here's how you would apply it to a UIViewController:
class MyViewController: UIViewController {
private var _view: RoundedCornerView {
return view as! RoundedCornerView
}
override func loadView() {
view = RoundedCornerView()
}
override func viewDidLoad() {
super.viewDidLoad()
_view.roundedCorners = [.topLeft, .topRight]
_view.roundedCornerRadii = CGSize(width: 10.0, height: 10.0)
}
}
Wrapping up Stuart's answer, you can have rounding corner method as the following:
#implementation UIView (RoundCorners)
- (void)applyRoundCorners:(UIRectCorner)corners radius:(CGFloat)radius {
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
}
#end
So to apply rounding corner, you simply do:
[self.imageView applyRoundCorners:UIRectCornerTopRight|UIRectCornerTopLeft radius:10];
I'd suggest defining a layer's mask. The mask itself should be a CAShapeLayer object with a dedicated path. You can use the next UIView extension (Swift 4.2):
extension UIView {
func round(corners: UIRectCorner, with radius: CGFloat) {
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = UIBezierPath(
roundedRect: bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius)
).cgPath
layer.mask = maskLayer
}
}

iOS animate/morph shape from circle to square

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)
}
}
}

Resources