Auto layout constraint and CALayer issue - ios

I am using this code to make my top corners to be rounded.
- (void)setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners
{
UIBezierPath *rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds
byRoundingCorners:corners
cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *shape = [[CAShapeLayer alloc] init];
[shape setPath:rounded.CGPath];
view.layer.mask = shape;
}
then in initWithFrame of superview method I make this:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
[self setMaskTo:imageView byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight];
But as result of it I don't see image. I am using auto layout constraint that works correct and I see image in case if I don't use rounding corner method above. But if I use it, I don't see image.
Seems if use draw rect callback of my view where image is placed, I can set rounding corners. But in this case if view contains other views it will add rounding to all subviews, if I will cycle it:
It works but for all subviews of course:
- (void)drawRect:(CGRect)rect {
// Drawing code
for (UIView *view in self.subviews)
{
[self setMaskTo:view byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight];
}
}
- (void)setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners
{
UIBezierPath *rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds
byRoundingCorners:corners
cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *shape = [[CAShapeLayer alloc] init];
[shape setPath:rounded.CGPath];
view.layer.mask = shape;
}
So how to detect when image view has got frame, I suppose that the frame is nil at start CALayer does not understand how to interact with view.

That's happen because you instantiate the view with frame CGRectZero, then you use that bounds for the mask so nothing in the view is inside the mask and therefore nothing is shown.
As a work around you can set a tag to UIImageView instance different than 0 and search in the subviews for that
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
imageView.tag = 111; //or any other number you want
- (void)drawRect:(CGRect)rect {
// Drawing code
for (UIView *view in self.subviews)
{
if (view.tag==111){[self setMaskTo:view byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight];
}}
}
- (void)setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners
{
UIBezierPath *rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds
byRoundingCorners:corners
cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *shape = [[CAShapeLayer alloc] init];
[shape setPath:rounded.CGPath];
view.layer.mask = shape;
}

If I understand it correctly, you want your UIView, which contains an image inside, to have rounded corners on the top side, right? Then you could just resize the mask layer in layoutSubviews.
Something like:
- (void)awakeFromNib {
[super awakeFromNib];
[self setMaskTo:self byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.layer.mask.frame = self.layer.bounds;
}
- (void)setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners {
UIBezierPath *rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds
byRoundingCorners:corners
cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *shape = [[CAShapeLayer alloc] init];
[shape setPath:rounded.CGPath];
view.layer.mask = shape;
view.layer.masksToBounds = YES;
}

Related

How to add top corners, top border and top shadow to a UIView at the same time

I need to add several kinds of effects on a UIView with rounding its top left and top right corners, white border and dark shadow on its rounded corner curve like the picture below:
I created a MSHTablePaneCell class inherited from UIView and then overrided its drawRect method:
static const CGFloat kCornerRadius = 15;
#implementation MSHTablePaneCell ()
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:(UIRectCornerTopLeft |UIRectCornerTopRight) cornerRadii:CGSizeMake(kCornerRadius, kCornerRadius)];
// 1. mask round corner
CAShapeLayer *roundLayer = [CAShapeLayer layer];
roundLayer.path = path.CGPath;
roundLayer.frame = self.bounds;
// roundLayer.cornerRadius = 10.0;
self.layer.mask = roundLayer;
// 2. add border to round corner curve
UIBezierPath *topCurvepath = [UIBezierPath bezierPath];
[topCurvepath moveToPoint:CGPointMake(0, kCornerRadius)];
[topCurvepath addQuadCurveToPoint:CGPointMake(kCornerRadius, 0) controlPoint:CGPointMake(0, 0)];
[topCurvepath addLineToPoint:CGPointMake(CGRectGetWidth(self.frame)-kCornerRadius, 0)];
[topCurvepath addQuadCurveToPoint:CGPointMake(CGRectGetWidth(self.frame), kCornerRadius) controlPoint:CGPointMake(CGRectGetWidth(self.frame), 0)];
CAShapeLayer *topBorderLayer = [CAShapeLayer layer];
topBorderLayer.lineWidth = 4.0;
topBorderLayer.borderColor = [UIColor redColor].CGColor;
topBorderLayer.path = topCurvepath.CGPath;
[self.contentView.layer addSublayer:topBorderLayer];
}
#end
However, with code above, I just got the final picture like below:
No matter which property I set, the topCurvePath just displayed with black background color, how I can fix this problem? Thanks in advance~
topBorderLayer.fillColor = [UIColor yourColor].CGColor

Corner Radius Using UIBezierPath

I'm Trying to round my view's corners using "UIBezierPath". I Only need to round topRight and Top left.
I have used Following code
-(void)setMaskOnView:(UIView *)givenView
{
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:givenView.bounds byRoundingCorners: (UIRectCornerTopLeft|UIRectCornerTopRight) cornerRadii:CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = givenView.bounds;
maskLayer.path = maskPath.CGPath;
givenView.layer.mask = maskLayer;
}
But my TopRight Corner does not round.
I've used
UIRectCornerAllCorners
But it does not round my right corners
Any thing i'm missing??
I suggest different approach. Load image with rounded top corners and set is as contents of CALayer. Set this layer as mask of your view layer. Update size of your mask layer in layoutSubivews of a given view or viewDidLayoutSubviews of given view controller.
Loading image as layer contenst
CALayer *maskLayer = [[CALayer alloc] init];
UIImage *maskImage = [UIImage imageNamed:#"mask_image.png" inBundle:[NSBundle mainBundle] compatibleWithTraitCollection:nil];
maskLayer.contents = (__bridge id _Nullable)(maskImage.CGImage);
mainLayer.mask = maskLayer
[EDIT] Answering your questions in comments
Either using CAShapeLayer or image as mask you have to resize your mask layer so it will have the same size as masked layer. If we are talking about UITableViewCell create your own derived cell and update mask shape in layoutSubviews . Below is example code (MyTableCell is loaded from storyboard):
#interface MyTableCell ()
#property (nonatomic, strong) CAShapeLayer *maskLayer;
#end
#implementation MyTableCell
- (void)awakeFromNib
{
self.maskLayer = [[CAShapeLayer alloc] init];
self.layer.mask = self.maskLayer;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.maskLayer.path = [self maskPath].CGPath;
}
- (UIBezierPath *)maskPath
{
return [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners: (UIRectCornerTopLeft|UIRectCornerTopRight) cornerRadii:CGSizeMake(10.0, 10.0)];
}
#end
I'm using this subclass
.h
#interface UIView (custom)
- (UIView *)setRoundedCorners:(UIRectCorner)corners withRadius:(CGFloat)radius;
#end
.m
#implementation UIView (custom)
- (UIView *)setRoundedCorners:(UIRectCorner)corners withRadius:(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;
return self;
}
#end
Using it like:
[YOURVIEW setRoundedCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight | UIRectCornerTopLeft | UIRectCornerTopRight withRadius:15];

UITableView: offsetting selectedBackgroundView

I've created a custom cell look that creates the look of a smaller cell and the illusion of space between them:
// SETTING UP A UIView IN ORDER TO CREATE ILLUSION OF SEPARATION BETWEEN CELLS
cell.contentView.backgroundColor = [UIColor clearColor];
UIView *clearRoundedCornerView = [[UIView alloc] initWithFrame:CGRectMake(10,10,310,110)];
clearRoundedCornerView.backgroundColor = [UIColor clearColor];
clearRoundedCornerView.layer.masksToBounds = YES;
clearRoundedCornerView.layer.cornerRadius = 3.0;
clearRoundedCornerView.layer.shadowOffset = CGSizeMake(-1, 1);
clearRoundedCornerView.layer.shadowOpacity = 0.5;
// Set up Mask with 2 rounded corners
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:clearRoundedCornerView.bounds byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerBottomLeft) cornerRadii:CGSizeMake(20.0, 20.0)];
CAShapeLayer *cornerMaskLayer = [CAShapeLayer layer];
[cornerMaskLayer setPath:path.CGPath];
clearRoundedCornerView.layer.mask = cornerMaskLayer;
// Make a transparent, stroked laker which will display the stroke
CAShapeLayer *strokeLayer = [CAShapeLayer layer];
strokeLayer.path = path.CGPath;
strokeLayer.fillColor = [UIColor clearColor].CGColor;
strokeLayer.strokeColor = [UIColor colorWithRed:(194/255.0) green:(77/255.0) blue:(1/255.0) alpha:1].CGColor;
strokeLayer.lineWidth = 5.0;
// Transparent view that will contain the stroke layer
UIView *strokeView = [[UIView alloc] initWithFrame:clearRoundedCornerView.bounds];
strokeView.userInteractionEnabled = NO; // in case your container view contains controls
[strokeView.layer addSublayer:strokeLayer];
[clearRoundedCornerView addSubview:strokeView];
[cell.contentView addSubview:clearRoundedCornerView];
In the following code I tried to modify selectedBackgroundView to match the view I created. The problem I am running into is that the X Y coordinates won't change and the selectedBackgroundView stays pinned to the upper left corner of the cell.
- (void)installSelectedBackgroundView{
UIView *bgCustomView = [[UIView alloc] initWithFrame:CGRectMake(10,10,310,110)];
bgCustomView.backgroundColor = [UIColor lightGrayColor];
bgCustomView.layer.masksToBounds = YES;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:bgCustomView.bounds byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerBottomLeft) cornerRadii:CGSizeMake(20.0, 20.0)];
CAShapeLayer *cornerMaskLayer = [CAShapeLayer layer];
[cornerMaskLayer setPath:path.CGPath];
bgCustomView.layer.mask = cornerMaskLayer;
self.selectedBackgroundView = bgCustomView;
[self setSelected:YES animated:YES];
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self installSelectedBackgroundView];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self installSelectedBackgroundView];
}
Not sure why this isn't working. Changing the X Y in bgCustomView doesn't seem to accomplish anything.
Thank you.
You need to change the origin of the path in installSelectedBackgroundView. As you've noticed, the origin of the bgCustomView is ignored; that's because as the selectedBackgroundView, it will have the same origin as the cell. So, instead, you need to offset the origin of the mask layer's path,
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectOffset(bgCustomView.bounds, 10, 10) byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerBottomLeft) cornerRadii:CGSizeMake(20.0, 20.0)];

Apply CAShapeLayer to multiple UIImageViews inside Cells

I'm trying to round the top corners of a UIImageView inside a UICollectionViewCell. It worked with my first code but the UICollectionViews performance went way down and started lagging. In a attempt to prevent this I tried to create a variable and just generate a CAShapeLayer for the first cell and then apply the same mask to the rest of the cells. Here is my code:
In cellForItemAtIndexPath
In if(maskLayer == nil){
UIBezierPath *maskPath;
maskPath = [UIBezierPath bezierPathWithRoundedRect:cell.image.bounds byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerTopRight) cornerRadii:CGSizeMake(3.0, 3.0)];
maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = cell.image.bounds;
maskLayer.path = maskPath.CGPath;
}
cell.image.layer.mask = maskLayer;
...
Far above:
#implementation MainViewViewController{
CAShapeLayer *maskLayer;
}
But this does not work. The mast is only getting applied to the last cell that comes in to view when scrolling and gets removed from the rest. Could this solution even work and why is this not working?
Thanks in advance!
EDIT 1
Tried to creata a subclass of UIImageView called RoundedImageView as suggested by #debugger and now the code looks like this:
RoundedImageView.m:
#import "RoundedImageView.h"
#implementation RoundedImageView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)viewDidLoad{
UIBezierPath *maskPath;
maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerTopRight) cornerRadii:CGSizeMake(3.0, 3.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
UIBezierPath *maskPath;
maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerTopRight) cornerRadii:CGSizeMake(3.0, 3.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
}
In my storyboard I have set the UIImageView class to RoundedImagView and in my custom UICollectionViewCell:
#property (strong, nonatomic) IBOutlet RoundedImageView *image;
But I the viewDidLoad or drawRect in RoundedImageView.m is not getting called at all.
for that you will have to create UIImageview extension class name RoundImageview. and add the following code in the ViewDidLoad method into class:
self.contentMode = UIViewContentModeScaleAspectFill;
self.clipsToBounds = YES;
self.layer.cornerRadius = self.frame.size.width/2; // Setting the corner radius
self.layer.masksToBounds = YES;
Now in the viewcontroller class add the UIImageview extension in the header and intitialize like below:
RoundImageview *roundimageview;
and in the cellForRowAtIndexPath method use the following method to add the image:
rounfimageview.image = yourimagename
I think above method will work..just try and test it.
Create CAShapeLayer for cell. When you create a single instance and try to add for all cell, it will add to the last one.
cell.image.layer.mask = maskLayer;
Here it will not create new copy when you assign masklayer.
Edit:
How single instance of layer can be sublayer for more than one layer?.
In cellForRowAtIndexPath: do the following
if (!cell) {
// create cell
//And create mask layer
cell.image.layer.mask = maskLayer; // - And do this
}
You don't need to override drawRect:, and you don't want to put code in viewDidLoad, because that's a UIViewController method, and won't be called in a UIImageView subclass. Since the image view is made in the storyboard, you should put your code in the initWithCoder: method of the UIImageView subclass,
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerTopRight) cornerRadii:CGSizeMake(3.0, 3.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
}
return self;
}

Creating a rounded-corner view

Good day!
Im trying to make my view (view in main view) make rounded corner. Im doing like this, but it doesn't work. Any ideas?
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
currenView = [[UIView alloc] init];
UIBezierPath *maskPath;
maskPath = [UIBezierPath bezierPathWithRoundedRect:currenView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(30.0, 30.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = currenView.bounds;
maskLayer.path = maskPath.CGPath;
currenView.layer.mask = maskLayer;
}
return self;
Try something like this:
view.layer.cornerRadius = 5.0;
view.layer.masksToBounds = YES;
for shadow:
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(1.0f, 1.0f);
view.layer.masksToBounds = NO;
view.layer.shadowRadius = 5.0f;
Make sure to import <QuartzCore/QuartzCore.h>
Here is the code. Alloc init a view and send to this method to get corners rounded. You can optionally round any of the corners u want. Also give shadow stroke color.
-(void) setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners withColor: (UIColor*) color
{
UIBezierPath* rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(9.0, 9.0)];
CAShapeLayer* shape = [[[CAShapeLayer alloc] init] autorelease];
[shape setPath:rounded.CGPath];
shape.strokeColor = [[UIColor grayColor] CGColor];
view.backgroundColor=color;
view.layer.mask = shape;
}
Call the method like this.
[self setMaskTo:ABCView byRoundingCorners:UIRectCornerAllCorners withColor:[UIColor greenColor]];
Stavash's solution seems to be correct, I have used it several times. If you are looking for an alternative or insist on using masklayers, see this answer: https://stackoverflow.com/a/13163693/936957
Your view has no size. its w and h is 0. Try something like,
currentView = [[UIView alloc] initWithFrame:CGRectMake(0,0 200,200)];
and then apply
currentView.layer.cornerRadius = 8.0;
currentView.layer.masksToBounds = YES;
currentView.layer.borderWidth = 1.0;

Resources