Question

I want to create a mask path whose final output will be a corner with a concave face.

I have created a UIBezierPath using the -bezierPathWithRoundedRect:byRoundingCorners:cornerRadii: method and specified that only one corner need to be rounded (in this example, the UIRectCornerBottomLeft) and I thought that by reversing this path, I should get what would have been cut in the first place.
For this, I am trying -bezierPathByReversingPath but it doesn't seem to make any difference (with or without as I am getting the normal bezier path and not the reversed)

This is what I have tried so far:

UIView *vwTest = [[UIView alloc] init];
[vwTest setFrame:CGRectMake(100.0f, 100.0f, 100.0f, 100.0f)];
[vwTest setBackgroundColor:[UIColor redColor]];
[self.view addSubview:vwTest];

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:vwTest.bounds
                                               byRoundingCorners:UIRectCornerBottomLeft
                                                     cornerRadii:CGSizeMake(50.0f, 50.0f)];

//get reverse path but doesn't seem to work as I think it should
maskPath = [maskPath bezierPathByReversingPath]; 

CAShapeLayer *maskLayer = [CAShapeLayer layer];
[maskLayer setFrame:vwTest.bounds];
[maskLayer setPath:maskPath.CGPath];

[vwTest.layer setMask:maskLayer];
[vwTest.layer setMasksToBounds:YES];
[vwTest setNeedsDisplay];

As per the image below, what I am trying to achieve is to show the red part of the UIView and eliminate the rest of the area.

maskPath Idea


For the time being, I am doing shit like subview on subview and syncing the color of one subview with the main view's background color (sucks but atleast I got the required output):

[self.view setBackgroundColor:[UIColor lightGrayColor]];

UIView *vwTest = [[UIView alloc] init];
[vwTest setFrame:CGRectMake(100.0f, 100.0f, 100.0f, 100.0f)];
[vwTest setBackgroundColor:[UIColor redColor]];
[self.view addSubview:vwTest];

UIView *vwPatch = [[UIView alloc] init];
[vwPatch setFrame:vwTest.bounds];
[vwPatch setBackgroundColor:vwTest.superview.backgroundColor];
[vwTest addSubview:vwPatch];

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:vwPatch.bounds
                                               byRoundingCorners:UIRectCornerBottomLeft
                                                     cornerRadii:CGSizeMake(50.0f, 50.0f)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
[maskLayer setFrame:vwPatch.bounds];
[maskLayer setPath:maskPath.CGPath];

[vwPatch.layer setMask:maskLayer];
[vwPatch.layer setMasksToBounds:YES];
[vwPatch setNeedsDisplay];

Someone, kindly point me in the right direction.

Was it helpful?

Solution

From looking at the code in the question I've assumed you wanted the bottom left corner (as you are using UIRectCornerBottomLeft) be concave with a radius of 30. You can't use bezierPathByReversingPath as all is does it literally reverse the path, it also says in the docs:

Reversing a path does not necessarily change the appearance of the path when rendered.

EDIT:
Now that an image has been added to the question, the OP wants the inverse of the shape:

CAShapeLayer *mask = [CAShapeLayer layer];
mask.frame = view.layer.bounds;

UIBezierPath *path = [UIBezierPath bezierPath];
CGFloat radius = 50;
CGRect rect = mask.bounds;
[path moveToPoint:CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect))];
[path addLineToPoint:CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect) - radius)];
[path addArcWithCenter:CGPointMake(CGRectGetMinX(rect) + radius, CGRectGetMaxY(rect) - radius) radius:radius startAngle:M_PI endAngle:M_PI_2 clockwise:NO];
[path closePath];

mask.path = path.CGPath;
view.layer.mask = mask;

The shape of the path is then red bit from the OPs image in the question.


Answer before image was posted - might be used for others

The following code will produce this shape:

shape

CGFloat cornerRadius = 30;

CGRect rect = CGRectMake(0, 0, 200, 100);

UIBezierPath *path = [UIBezierPath bezierPath];

// Draw the complete sides
[path moveToPoint:rect.origin];
[path addLineToPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect))];
[path addLineToPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect))];

// Stop short for the start of the arc
[path addLineToPoint:CGPointMake(CGRectGetMinX(rect) + cornerRadius, CGRectGetMaxY(rect))];

// Concave arc
[path addArcWithCenter:CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect)) radius:cornerRadius startAngle:0 endAngle:M_PI_2 * 3 clockwise:NO];

// Complete the path
[path closePath];

Update: Useful category

Category on UIBezierPath so you can pick which corners (bit mask of UIRectCorners) and a radius.

UIBezierPath+RDHConcaveCorners.h

@interface UIBezierPath (RDHConcaveCorners)

+(instancetype)bezierPathWithRoundedRect:(CGRect)rect byConcaveRoundingCorners:(UIRectCorner)corners cornerRadius:(CGFloat)radius;

@end

UIBezierPath+RDHConcaveCorners.m

@implementation UIBezierPath (RDHConcaveCorners)

+(instancetype)bezierPathWithRoundedRect:(CGRect)rect byConcaveRoundingCorners:(UIRectCorner)corners cornerRadius:(CGFloat)cornerRadius
{
    CGFloat halfWidth = CGRectGetWidth(rect) / 2;
    CGFloat halfHeight = CGRectGetHeight(rect) / 2;
    if (cornerRadius > halfWidth || cornerRadius > halfHeight) {
        cornerRadius = MIN(halfWidth, halfHeight);
    }

    UIBezierPath *path = [self bezierPath];

    CGPoint topLeft = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
    if (corners & UIRectCornerTopLeft) {
        [path moveToPoint:CGPointMake(topLeft.x, topLeft.y + cornerRadius)];
        [path addArcWithCenter:topLeft radius:cornerRadius startAngle:M_PI_2 endAngle:0 clockwise:NO];
    } else {
        [path moveToPoint:topLeft];
    }

    CGPoint topRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
    if (corners & UIRectCornerTopRight) {
        [path addLineToPoint:CGPointMake(topRight.x - cornerRadius, topRight.y)];
        [path addArcWithCenter:topRight radius:cornerRadius startAngle:M_PI endAngle:M_PI_2 clockwise:NO];
    } else {
        [path addLineToPoint:topRight];
    }

    CGPoint bottomRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect));
    if (corners & UIRectCornerBottomRight) {
        [path addLineToPoint:CGPointMake(bottomRight.x, bottomRight.y - cornerRadius)];
        [path addArcWithCenter:bottomRight radius:cornerRadius startAngle:M_PI_2 * 3 endAngle:M_PI clockwise:NO];

    } else {
        [path addLineToPoint:bottomRight];
    }

    CGPoint bottomLeft = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect));
    if (corners & UIRectCornerBottomLeft) {
        [path addLineToPoint:CGPointMake(bottomLeft.x + cornerRadius, bottomLeft.y)];
        [path addArcWithCenter:bottomLeft radius:cornerRadius startAngle:0 endAngle:M_PI_2 * 3 clockwise:NO];
    } else {
        [path addLineToPoint:bottomLeft];
    }

    // Complete the path
    [path closePath];

    return path;
}

@end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top