UIBezierPath减去路径

jad*_*ler 47 iphone objective-c uibezierpath

通过使用[UIBezierPath bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:],我可以创建一个圆形视图,如下所示:

圆形视图

我怎样才能从这一个(或其他方式)中减去另一条路径,以创建这样的路径:

减去视图

我有什么方法可以做这样的事情吗?伪代码:

UIBezierPath *bigMaskPath = [UIBezierPath bezierPathWithRoundedRect:bigView.bounds 
                                 byRoundingCorners:(UIRectCornerTopLeft|UIRectCornerTopRight)
                                       cornerRadii:CGSizeMake(18, 18)];
UIBezierPath *smallMaskPath = [UIBezierPath bezierPathWithRoundedRect:smalLView.bounds 
                                     byRoundingCorners:(UIRectCornerTopLeft|UIRectCornerTopRight)
                                           cornerRadii:CGSizeMake(18, 18)];

UIBezierPath *finalPath = [UIBezierPath pathBySubtractingPath:smallMaskPath fromPath:bigMaskPath];
Run Code Online (Sandbox Code Playgroud)

Pat*_*pel 69

实际上,对于大多数情况来说,有一种更简单的方法,例如Swift:

path.append(cutout.reversing())
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为默认填充规则是非零缠绕规则.

  • 哇!超级优雅,令人惊叹! (2认同)
  • @BooberBunz 是的 (2认同)

rob*_*off 59

如果你想要减去减去的路径,你就可以自己动手了.Apple不提供返回(或只是描边)从一个路径减去另一个路径的API.

如果您只想填充减去的路径(如示例图像中所示),则可以使用剪切路径来完成.但是你必须使用一个技巧.向剪切路径添加路径时,新剪切路径是旧剪切路径和添加路径的交集.因此,如果您只是添加smallMaskPath到剪切路径,您将最终只填充内部区域smallMaskPath,这与您想要的相反.

你需要做的是相交与现有的剪辑路径相反smallMaskPath.幸运的是,你可以使用偶数奇数绕组规则轻松地做到这一点.您可以在Quartz 2D Programming Guide中阅读偶数规则.

基本思想是我们创建一个包含两个子路径的复合路径:您smallMaskPath和一个巨大的矩形,它完全包围您smallMaskPath可能想要填充的所有其他像素.由于奇偶规则,内部的每个像素都smallMaskPath将被视为复合路径的外部,并且外部的每个像素都smallMaskPath将被视为复合路径的内部.

所以让我们创建这个复合路径.我们将从巨大的矩形开始.并且没有比无限长方形更大的矩形:

UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectInfinite];
Run Code Online (Sandbox Code Playgroud)

现在我们通过添加它将它变成复合路径smallMaskPath:

[clipPath appendPath:smallMaskPath];
Run Code Online (Sandbox Code Playgroud)

接下来,我们设置使用奇偶规则的路径:

clipPath.usesEvenOddFillRule = YES;
Run Code Online (Sandbox Code Playgroud)

在剪切到此路径之前,我们应该保存图形状态,以便在完成后撤消对剪切路径的更改:

CGContextSaveGState(UIGraphicsGetCurrentContext()); {
Run Code Online (Sandbox Code Playgroud)

现在我们可以修改剪切路径:

    [clipPath addClip];
Run Code Online (Sandbox Code Playgroud)

我们可以填写bigMaskPath:

    [[UIColor orangeColor] setFill];
    [bigMaskPath fill];
Run Code Online (Sandbox Code Playgroud)

最后,我们恢复图形状态,撤消对剪切路径的更改:

} CGContextRestoreGState(UIGraphicsGetCurrentContext());
Run Code Online (Sandbox Code Playgroud)

如果你想要复制/粘贴代码,这里是代码:

UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectInfinite];
[clipPath appendPath:smallMaskPath];
clipPath.usesEvenOddFillRule = YES;

CGContextSaveGState(UIGraphicsGetCurrentContext()); {
    [clipPath addClip];
    [[UIColor orangeColor] setFill];
    [bigMaskPath fill];
} CGContextRestoreGState(UIGraphicsGetCurrentContext());
Run Code Online (Sandbox Code Playgroud)

  • 这比需要的工作更多.创建一个由内部和外部圆形组成的路径并使用奇偶规则填充它很简单.您无需对剪辑路径执行任何操作. (7认同)
  • 我喜欢你用来把GState放在大括号之间的风格!! (5认同)

NSR*_*der 34

这应该这样做,根据你的喜好调整大小:

CGRect outerRect = {0, 0, 200, 200};
CGRect innerRect  = CGRectInset(outerRect,  30, 30);

UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:outerRect cornerRadius:10];

[path appendPath:[UIBezierPath bezierPathWithRoundedRect:innerRect cornerRadius:5]];
path.usesEvenOddFillRule = YES;

[[UIColor orangeColor] set];
[path fill];
Run Code Online (Sandbox Code Playgroud)

获得后续效果的另一种非常简单的方法是绘制外部圆形,更改颜色,并在其上绘制内部.


Fat*_*tie 9

2019 - 太容易了

我对其他答案感到惊讶,因为这非常容易做到。可能有一些我不明白的要求。但:

p = UIBezierPath(rect: .. )
let hole = UIBezierPath(ovalIn: ... )
p.append(hole.reversing())
p.usesEvenOddFillRule = false
Run Code Online (Sandbox Code Playgroud)

无论您是否进行切割,这都非常有效。

没有问题,即使他们这样做的重叠:

在此处输入图片说明

在此处输入图片说明

(这是制作“缺口”或“缩进”的好方法。)

该技术是

  1. 外径顺时针,内径逆时针
  2. 使用计数规则又名非零规则

(在示例中,我实际上是将它用作 UIView 上的掩码...)

layerToUseAsUIViewMask.path = p
layer.mask = layerToUseAsUIViewMask
Run Code Online (Sandbox Code Playgroud)

为了保存任何输入孔代码的人......

let hole = UIBezierPath(ovalIn: CGRect(
    origin: CGPoint(x: 70, y: 10), .. use -10 for the second demo above.
    size: CGSize(width: 50, height: 50))
)
Run Code Online (Sandbox Code Playgroud)

注意如果您不确定这两条路径的运行方式,当然只需尝试每一条

p.append(hole.reversing()) // that's acw currently on iOS
Run Code Online (Sandbox Code Playgroud)

p.append(hole) // that's cw currently on iOS
Run Code Online (Sandbox Code Playgroud)

直到它工作。


类似的有用提示:

制作发光盒:

/sf/answers/4136497991/


gbk*_*gbk 7

使用@Patrick Pijnappel回答,您可以准备测试操场以进行快速测试

import UIKit
import PlaygroundSupport

let view = UIView(frame: CGRect(x: 0, y: 0, width: 375, height: 647))
view.backgroundColor = UIColor.green

let someView = UIView(frame: CGRect(x:50, y: 50, width:250, height:250))
someView.backgroundColor = UIColor.red
view.addSubview(someView)

let shapeLayer = CAShapeLayer()
shapeLayer.frame = someView.bounds
shapeLayer.path = UIBezierPath(roundedRect: someView.bounds, 
                               byRoundingCorners: [UIRectCorner.bottomLeft,UIRectCorner.bottomRight] ,
                                cornerRadii: CGSize(width: 5.0, height: 5.0)).cgPath

someView.layer.mask = shapeLayer
someView.layer.masksToBounds = true

let rect = CGRect(x:0, y:0, width:200, height:100)
let cornerRadius:CGFloat = 5
let subPathSideSize:CGFloat = 25

let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
let leftSubPath = UIBezierPath(arcCenter: CGPoint(x:0, y:rect.height / 2), 
                                radius: subPathSideSize / 2, startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI + M_PI_2), clockwise: false)
leftSubPath.close()

let rightSubPath = UIBezierPath(arcCenter: CGPoint(x:rect.width, y:rect.height / 2), 
                               radius: subPathSideSize / 2, startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI + M_PI_2), clockwise: true)
rightSubPath.close()

path.append(leftSubPath)
path.append(rightSubPath.reversing())
path.append(path)

let mask = CAShapeLayer()
mask.frame = shapeLayer.bounds
mask.path = path.cgPath

someView.layer.mask = mask 

view
PlaygroundPage.current.liveView = view
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

在此输入图像描述


Tay*_*day 5

我一直在靠墙试图弄清楚如何使用多个重叠的CGPath来做这件事.如果您重叠多次,则重新填充上面提供的解决方案.事实证明,通过多个重叠路径真正获得"减法"效果的方法是将上下文的混合模式设置为清除.

CGContextSetBlendMode(ctx, kCGBlendModeClear);