通过使用[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];
答案 0 :(得分:60)
如果你想要减去减去的路径,你就可以自己动手了。 Apple没有提供返回(或只是描边)从一个路径减去另一个路径的API。
如果您只想填充减去的路径(如示例图像中所示),则可以使用剪切路径来完成。但是你必须使用一个技巧。向剪切路径添加路径时,新剪切路径是旧剪切路径和添加路径的交叉点。因此,如果您只是将smallMaskPath
添加到剪切路径,则最终只会填充smallMaskPath
内的区域,这与您想要的区域相反。
您需要做的是将现有剪切路径与smallMaskPath
的反向相交。幸运的是,你可以使用偶数奇数绕组规则轻松地做到这一点。您可以在Quartz 2D Programming Guide中了解偶数规则。
基本思想是我们创建一个包含两个子路径的复合路径:您的smallMaskPath
和一个完全包围smallMaskPath
的巨大矩形以及您可能想要填充的每个其他像素。由于奇偶规则,smallMaskPath
内的每个像素都将被视为复合路径之外,smallMaskPath
之外的每个像素都将被视为复合路径的内部。
所以让我们创建这个复合路径。我们将从巨大的矩形开始。并且没有比无限长方形更大的矩形:
UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectInfinite];
现在我们通过向其添加smallMaskPath
使其成为复合路径:
[clipPath appendPath:smallMaskPath];
接下来,我们设置使用偶数规则的路径:
clipPath.usesEvenOddFillRule = YES;
在剪切到此路径之前,我们应该保存图形状态,以便在完成后撤消对剪切路径的更改:
CGContextSaveGState(UIGraphicsGetCurrentContext()); {
现在我们可以修改剪切路径:
[clipPath addClip];
我们可以填写bigMaskPath
:
[[UIColor orangeColor] setFill];
[bigMaskPath fill];
最后,我们恢复图形状态,撤消对剪切路径的更改:
} CGContextRestoreGState(UIGraphicsGetCurrentContext());
如果你想复制/粘贴代码,这里是代码:
UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectInfinite];
[clipPath appendPath:smallMaskPath];
clipPath.usesEvenOddFillRule = YES;
CGContextSaveGState(UIGraphicsGetCurrentContext()); {
[clipPath addClip];
[[UIColor orangeColor] setFill];
[bigMaskPath fill];
} CGContextRestoreGState(UIGraphicsGetCurrentContext());
答案 1 :(得分:32)
应该这样做,根据需要调整大小:
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];
获得效果的另一种非常简单的方法是绘制外部圆形,更改颜色,并在其上绘制内部效果。
答案 2 :(得分:5)
我一直在靠墙试图弄清楚如何使用多个重叠的CGPath来做这件事。如果您重叠多次,则重新填充上面提供的解决方案。事实证明,通过多个重叠路径真正获得“减法”效果的方法是将上下文的混合模式设置为清除。
CGContextSetBlendMode(ctx, kCGBlendModeClear);
答案 3 :(得分:5)
使用@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
答案 4 :(得分:3)
我对其他答案感到惊讶,因为这非常容易做到。可能有一些我不理解的要求。但是:
p = UIBezierPath(rect: .. )
let hole = UIBezierPath(ovalIn: ... )
p.append(hole.reversing())
p.usesEvenOddFillRule = false
无论您有没有剪裁,这都可以很好地工作。
和
(这是制作“缺口”或“凹痕”的好方法。)
技术是
(在示例中,我实际上是将其用作UIView上的蒙版...)
layerToUseAsUIViewMask.path = p
layer.mask = layerToUseAsUIViewMask
要保存输入密码的任何人,...
let hole = UIBezierPath(ovalIn: CGRect(
origin: CGPoint(x: 70, y: 10), .. use -10 for the second demo above.
size: CGSize(width: 50, height: 50))
)
注意如果您不确定两条路径的运行方式,请尝试以下每种方法
p.append(hole.reversing()) // that's acw currently on iOS
和
p.append(hole) // that's cw currently on iOS
直到它起作用。
类似的有用提示:
答案 5 :(得分:2)
Patrick使用non-zero winding rule为用户NSResponder的答案提供了改进/替代方案。对于正在寻找扩展答案的任何人来说,这是Swift的完整实现。
UIGraphicsBeginImageContextWithOptions(CGSize(width: 200, height: 200), false, 0.0)
let context = UIGraphicsGetCurrentContext()
let rectPath = UIBezierPath(roundedRect: CGRectMake(0, 0, 200, 200), cornerRadius: 10)
var cutoutPath = UIBezierPath(roundedRect: CGRectMake(30, 30, 140, 140), cornerRadius: 10)
rectPath.appendPath(cutoutPath.bezierPathByReversingPath())
UIColor.orangeColor().set()
outerForegroundPath.fill()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
您可以将其放入故事板以查看和播放输出。
答案 6 :(得分:0)
我可以使用CGPath
和CGPathAddLineToPoint
创建CGPathAddArcToPoint
,而不是减去。这也可以使用类似代码的UIBiezerPath
来完成。