CAShapeLayer rounding corners impacts performance - ios

I have a UICollectionView (iPad) that has jarry scrolling.Each cell has a white rectangle UIView (with all rounded corners) that has another UIImageView in front of it with only top two corners rounded. See this link below for how this cell looks - i.stack.imgur.com/Ptlig.png
Here's the code for the UICollectionViewCell
UIView * bgView = [[UIView alloc] initWithFrame:CGRectMake(5, 5, (self.contentView.frame.size.width - 10), (self.contentView.frame.size.width - 10)*IMAGE_RATIO + 43.5)];
bgView.backgroundColor = [UIColor clearColor];
bgView.layer.cornerRadius = 10;
bgView.layer.borderWidth = 0.5f;
bgView.layer.borderColor = [UIColor colorWithRed: 179/255.0 green:181/255.0 blue:184/255.0 alpha:1.0].CGColor;
bgView.layer.backgroundColor = [UIColor whiteColor].CGColor;
bgView.layer.shouldRasterize = YES;
bgView.layer.rasterizationScale = [UIScreen mainScreen].scale;
[self.contentView addSubview:bgView];
self.parablePost = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, (self.contentView.frame.size.width - 10), (self.contentView.frame.size.width-10)*IMAGE_RATIO)];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.parablePost.bounds byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerTopRight) cornerRadii:CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.parablePost.bounds;
maskLayer.path = maskPath.CGPath;
self.parablePost.layer.mask = maskLayer;
[self.contentView addSubview:self.parablePost];
When this line "self.parablePost.layer.mask = maskLayer" is commented, scrolling is smooth. So the CAShapeLayer is causing performance issues. I tried rasterize and hand drawing instead of using UIBezierPath but it does not improve the experience.
Is there a faster way to round two corners of UIImageView?
EDIT:
I finally added static images to simulate rounded corners on the top two corners of the UIImageView that gave me the same look without the scroll jitter. Surprised that CAShapeLayer is a performance drain.

masks are expensive... (they might even be rendered in software). try to think of another way to get the look you want. for example, pre-render things to an image...

Related

UIView with cut out segment and shadow

My problem is following: I'm trying to cut out a segment out of custom UIView and apply a shadow effect to this view.
In a custom UIView class, I did this:
Created two layers - shadow and mask. Added a shadow layer as a sublayer for this custom view. Then I created a new view, set its mask as the mask layer and added it as a subview to the custom view.
self.backgroundColor = [UIColor clearColor];
CGFloat radius = 40;
float startAngle = -M_PI;
float endAngle = 0;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.bounds];
[path moveToPoint:CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMaxY(self.bounds)+radius/1.8)];
[path addArcWithCenter:CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMaxY(self.bounds)+radius/1.8) radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
CAShapeLayer *shadowLayer = [[CAShapeLayer alloc] init];
shadowLayer.frame = self.bounds;
shadowLayer.path = path.CGPath;
shadowLayer.shadowColor = [UIColor grayColor].CGColor;
shadowLayer.shadowOpacity = 0.5;
shadowLayer.shadowRadius = 2;
shadowLayer.masksToBounds = NO;
shadowLayer.shadowOffset = CGSizeMake(2.0, 2.0);
shadowLayer.fillRule = kCAFillRuleEvenOdd;
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.masksToBounds = NO;
maskLayer.path = path.CGPath;
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.fillColor = [UIColor whiteColor].CGColor;
[self.layer addSublayer:shadowLayer];
UIView *view = [[UIView alloc] initWithFrame:self.bounds];
view.backgroundColor = [UIColor whiteColor];
view.layer.mask = maskLayer;
[self addSubview:view];
That is what I want to achieve -
and this is what I actually get
If I set clipToBounds = YES, it will cut off the desired shadow effect.
There would be absolutely no problem, if I wanted to cut of a semicircle. Because in this case the semicircle path fully sits inside of view bounds.
But I do need to achieve a result shown in the first image.
I was thinking about building the path line by line, but the problem occurs when it comes to this arc, and the result will probably not be so accurate.
Does anyone have ideas how it could be done?
Thank you!
EDIT:
If the issue is that the path is wrong, do no composite the path in code using a circle and guessing the coordinates. Instead you should export the coordinates actual artwork and use the artwork coordinates for the curve. It looks like you have a sketch file so the easiest way to do this is to copy and paste into paint code which will give you the code for the curve, but in the event you don't have paint code (you should, its amazing for this kind of stuff) you can export the curve as SVG from sketch. If you open the resulting SVG in a text editor you will see its in human readable XML and you can extract the control points from your Bezier curve from there.

Bottom Corner Radius and shadow only at bottom ofUIView in Objective C

I want to add radius to bottom left and bottom right corners of an UIView and then drop shadow only at bottom of the same UIView.
I have gone through solutions in which all corners are provided with radius and then shadow. That is working fine. But when I use UIBeizerPath to add radius to bottom corners the shadow property doesn't seem to work.
I am using Objective-C and XCode 8.1.
How can I do it?
Using below code bottom corners get their radius but shadow properties doesn't work.
UIView *testView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 40)];
[testView setBackgroundColor:[UIColor yellowColor]];
// shadow
testView.layer.shadowColor = [UIColor colorWithRed:156.0f/255.0f green:153.0f/255.0f blue:153.0f/255.0f alpha:1.0f].CGColor;
testView.layer.shadowOffset = CGSizeMake(0.0f, 2.0f);
testView.layer.shadowRadius = 4.0f;
testView.layer.shadowOpacity = 0.5f;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0.0, 0.0)];
[path addLineToPoint:CGPointMake(0.0, CGRectGetHeight(testView.frame))];
[path addLineToPoint:CGPointMake(CGRectGetWidth(testView.frame), CGRectGetHeight(testView.frame))];
[path addLineToPoint:CGPointMake(CGRectGetWidth(testView.frame), 0.0)];
testView.layer.shadowPath = path.CGPath;
//bottom corners radius
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:testView.bounds
byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight)
cornerRadii:CGSizeMake(2.0, 2.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.view.bounds;
maskLayer.path = maskPath.CGPath;
testView.layer.mask = maskLayer;
The mask is masking the shadow. You need to have two view one inside the other. Apply the mask to the inner view and the shadow to the outer view.

Setting CALayer maskToBounds causing layer to disappear

I am trying to create a circle which is filled depending upon a certain percentage. Here is pic of the effect I am going for:
I have a UIView and to this I add a CAShapeLayer which draws the circle. I am then creating another UIShapeLayer as a square to match the UIView containing the circle. Then setting the height of this depending on the figure. So if the square is 100px high and the value is 10% then I set the square to 10px so that it fills 10% of circle.
The image below shows 50% of the circle being filled. As you can see it covers the UIView as well as the circle. However when I try to set the CAShapeLayer to mask to bounds ( circle.maskToBounds) the circle complete disappears along with the square that I am adding as it's subview.
Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
[self drawCircleInView:view];
[self.view addSubview:view];
}
- (void)drawCircleInView:(UIView *)v
{
// Set up the shape of the circle
int radius = v.frame.size.height / 2;
CAShapeLayer *circle = [CAShapeLayer layer];
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2*radius, 2*radius)
cornerRadius:radius].CGPath;
// Center the shape in self.view
circle.position = CGPointMake(CGRectGetMidX(v.bounds)-radius,
CGRectGetMidY(v.bounds)-radius);
// Configure the apperence of the circle
circle.fillColor = [UIColor blueColor].CGColor;
circle.strokeColor = [UIColor clearColor].CGColor;
circle.lineWidth = 5;
circle.masksToBounds = YES; // HERE IS THE LINE OF CODE THAT MAKES THE CIRCLE DISAPPEAR
CAShapeLayer *sublayer = [CAShapeLayer layer];
sublayer.backgroundColor = [UIColor orangeColor].CGColor;
sublayer.opacity = .5f;
sublayer.frame = CGRectMake(0, 100, 200, 100);
[circle addSublayer:sublayer];
// Add to parent layer
[v.layer addSublayer:circle];
}
I am wondering why setting circle.masksToBounds = YES is making the circle and the sublayer disappear completely. My understanding is that by setting this it should only show the sublayer over the circle.
Many thanks in advance.
Try this:
- (void)drawCircleInView:(UIView *)v {
// Set up the shape of the circle
CGSize size = v.bounds.size;
CALayer *layer = [CALayer layer];
layer.frame = v.bounds;
layer.backgroundColor = [UIColor blueColor].CGColor;
CALayer *sublayer = [CALayer layer];
sublayer.frame = CGRectMake(0, size.height/2, size.width, size.height/2);
sublayer.backgroundColor = [UIColor orangeColor].CGColor;
sublayer.opacity = .5f;
CAShapeLayer *mask = [CAShapeLayer layer];
mask.frame = v.bounds;
mask.path = [UIBezierPath bezierPathWithOvalInRect:v.bounds].CGPath;
mask.fillColor = [UIColor blackColor].CGColor;
mask.strokeColor = [UIColor clearColor].CGColor;
[layer addSublayer:sublayer];
[layer setMask:mask];
// Add to parent layer
[v.layer addSublayer:layer];
}
maskToBounds is "to bounds" you are not setting bounds nor frame.
It's not necessary, but you'd better to set frame of CAShapeLayer, to prevent unnecessary confusion.
bounds is rectangle, not CAShapeLayer's shape. Use mask layer to mask by shape instead.
circle and sublayer is not need to be CAShapeLayer because it will be masked by shaped mask layer.
To achieve this use a square image with its center circle as transparent. like this image
How to use this image for this effect -
Add a square layer (with lighter color/ or non fill color) first. above that add a layer with the fill color with sane rect. and above all place this image. and change the frame of middle layer that has the fill color to achieve desired effect

UIImageView with border and rounded corner with image pops out from the border edges

i'm trying to make a UIImageView with rounder corner and white border, i have subclassed a UIImageView, this is the code:
MyUIImageView.h
#interface MyUIImageView : UIImageView
#end
MyUIImageView.m
#implementation MyUIImageView
-(void)layoutSubviews
{
self.layer.cornerRadius = CGRectGetWidth(self.frame)/2.f;
self.layer.borderColor = [[UIColor whiteColor] CGColor];
self.layer.borderWidth = kLineWidth;
self.layer.masksToBounds = YES;
self.clipsToBounds = YES;
self.contentMode = UIViewContentModeScaleAspectFill;
self.backgroundColor = [UIColor colorWithRed:0.82 green:0.82 blue:0.83 alpha:1];
}
#end
this is the result:
seems fine, but there is a problem as you can see from here:
the image pops out from the borders edge, how i can avoid this problem? how i can cut the image exactly at the edge of the border?
Perhaps a better solution doesn't involve making another View at all - with two views you greatly increase complexity for animation etc, not to mention overhead to keep track of and manipulate both.
I'd instead create a shape layer and add it as a sublayer. Something like this:
CAShapeLayer border = [[CAShapeLayer alloc] init];
border.path = [UIBezierPath bezierPathWithRoundedRect: self.bounds cornerRadius: self.layer.cornerRadius];
border.strokeColor = [UIColor whiteColor];
border.fillColor = [UIColor clearColor];
self.layer.addSublayer(border)
The benefit of doing it this way is that you can add it as a method on your UIImageView subclass if you wish. You can add a border to an object and forget about it, as long as you're not changing the frame of the base object. Transforms etc affect sublayers so you can scale, rotate, etc and not have gross edges.
Hope this helps!
Create a custom border like this:
UIImage *image = [UIImage imageNamed:#"spongebob.jpg"];
UIView *borderView = [[UIView alloc] initWithFrame:CGRectMake(30, 30, 200, 200)];
[borderView.layer setMasksToBounds:YES];
[borderView.layer setCornerRadius:borderView.frame.size.width/2.0f];
[borderView setBackgroundColor:[UIColor whiteColor]];
int borderWidth = 3.0f;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(borderWidth, borderWidth, borderView.frame.size.width-borderWidth*2, borderView.frame.size.height-borderWidth*2)];
[imageView.layer setCornerRadius:imageView.frame.size.width/2.0f];
[imageView.layer setMasksToBounds:YES];
[imageView setImage:image];
[borderView addSubview:imageView];
[self.view addSubview:borderView];
Now you image does not pop out of the border.
Hope this helps :)

Why does applying a bezierPathWithRoundedRect mask yield a different result from setting the layer's cornerRadius?

I typically use the cornerRadius property to apply rounded corners to view layers. When the views only need rounded top or bottom corners, I apply a mask with a bezier path instead (using bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:). When both approaches are combined in the same view hierarchy, the rounded corners are not properly aligned, as illustrated below:
This simplified example can be reproduced with the following code:
#interface ViewController : UIViewController
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
CGRect frame = CGRectMake(50.0, 50.0, 100.0, 100.0);
CGFloat radius = 20.0;
// Apply cornerRadius to green backdrop view.
UIView *backdropView = [[UIView alloc] initWithFrame:frame];
backdropView.backgroundColor = [UIColor greenColor];
backdropView.layer.cornerRadius = radius;
[self.view addSubview:backdropView];
// Apply bezier path mask to black front view.
UIView *frontView = [[UIView alloc] initWithFrame:frame];
frontView.backgroundColor = [UIColor blackColor];
CAShapeLayer * maskLayer = [CAShapeLayer layer];
maskLayer.path = [UIBezierPath bezierPathWithRoundedRect:frontView.bounds
byRoundingCorners:UIRectCornerAllCorners
cornerRadii:CGSizeMake(radius, radius)].CGPath;
frontView.layer.mask = maskLayer;
[self.view addSubview:frontView];
}
#end
Setting the shouldRasterize property of the different layers involved did not solve the issue. I would like to understand why this happens. A possible workaround would be to always apply the bezier path mask rather than simply setting the corner radius but that feels like overkill.
This website does a pretty good job explaining: http://www.paintcodeapp.com/blogpost/code-for-ios-7-rounded-rectangles (in short, it's an iOS7 only thing)
For an extra illustration, see:
http://www.mani.de/backstage/?p=483

Resources