【问题标题】:Reverse UIBezierPath for single sided corners单边角的反向 UIBezierPath
【发布时间】:2023-05-25 06:38:01
【问题描述】:

我想创建一个蒙版路径,其最终输出将是一个带有凹面的角。

我使用-bezierPathWithRoundedRect:byRoundingCorners:cornerRadii: 方法创建了一个UIBezierPath 并指定只需要圆角(在本例中为UIRectCornerBottomLeft),我认为通过反转这个路径,我应该首先得到会被削减的东西。
为此,我正在尝试-bezierPathByReversingPath,但它似乎没有任何区别(有或没有,因为我得到的是正常的贝塞尔路径,而不是相反的

这是我迄今为止尝试过的:

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

根据下图,我想要实现的是显示UIView 的红色部分并消除该区域的其余部分。


目前,我在子视图上做子视图之类的事情,并将一个子视图的颜色与主视图的背景颜色同步(很糟糕,但至少我得到了所需的输出):

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

有人,请指点我正确的方向。

【问题讨论】:

  • 我认为您需要手动绘制路径,然后使用addArcWithCenter:radius:startAngle:endAngle:clockwise:。你能在你的问题中发布你想要的示例图片吗?它会帮助你回答:)
  • 我尝试了-bezierPathWithArcCenter:radius:startAngle:endAngle:clockwise:,但也无法到达任何地方。无论如何...我会发布一张图片并将其添加到问题中。
  • @Rich:好的,我添加了一张图片......现在,我将研究你的答案,了解如何实现这一点。

标签: ios objective-c uiview uibezierpath cashapelayer


【解决方案1】:

通过查看问题中的代码,我假设您希望左下角(因为您正在使用 UIRectCornerBottomLeft)是半径为 30 的凹面。您不能像所有情况一样使用 bezierPathByReversingPath它从字面上反转了路径,它还在文档中说:

反转路径并不一定会在渲染时改变路径的外观。

编辑:
现在已将图像添加到问题中,OP 想要形状的倒数:

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;

路径的形状是问题中 OPs 图像的红色位。


在发布图片之前回答 - 可能会用于其他人

以下代码将产生这种形状:

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

更新:有用的类别

UIBezierPath 上的类别,以便您可以选择哪些角(UIRectCorners 的位掩码)和半径。

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

【讨论】:

  • 更新了UIBezierPath 上一个有用类别的代码,用于选择单个角(或所有角)!
  • @staticVoidMan 现在你已经添加了一个图像,我现在看到你想要通过保留路径来做什么 - 你想要它的反面。我已经用我最初提供的更有用的解决方案更新了我的答案!
  • @Rich:太棒了。第一个代码示例正是我想要的。我希望-bezierPathByReversingPath 一步完成,但也感谢贝塞尔路径数据和该类别的荣誉。感谢您的帮助:)
  • @Rich 嘿伙计,你能帮忙吗?我想在 swift 上使用你的代码,我已经为目标 c 代码创建了桥梁,但我不知道应该如何在 swift 上使用代码