使用相交的 UIBezierPath 屏蔽 UIEffectView
Masking a UIEffectView with intersecting UIBezierPath
我想在 tableView 上应用带有模糊效果的 UIEffectView
,但每个单元格中的圆形 UIImageView
对象都显示出来。我使用了一个掩码并改编了 的解决方案来创建一个方法,该方法将迭代地在每个单元格上方切出圆圈:
func cutCircle(inView view: UIView, rect1: CGRect, rect2: CGRect?) {
// Create new path and mask
let newMask = CAShapeLayer()
// Create path to clip
let newClipPath = UIBezierPath(rect: view.bounds)
let path1 = UIBezierPath(ovalIn: rect1)
newClipPath.append(path1)
if let rect2 = rect2 {
let path2 = UIBezierPath(ovalIn: rect2)
//FIXME: Need a way to get a union of both paths!
//newClipPath.append(path2)
}
// If view already has a mask
if let originalMask = view.layer.mask, let originalShape = originalMask as? CAShapeLayer, let originalPath = originalShape.path {
// Create bezierpath from original mask's path
let originalBezierPath = UIBezierPath(cgPath: originalPath)
// Append view's bounds to "reset" the mask path before we re-apply the original
newClipPath.append(UIBezierPath(rect: view.bounds))
// Combine new and original paths
newClipPath.append(originalBezierPath)
}
// Apply new mask
newMask.path = newClipPath.cgPath
newMask.fillRule = .evenOdd
view.layer.mask = newMask
}
此函数在 UIEffectView
上为每个可见的表格视图单元格调用,使用:for cell in tableView.visibleCells()
。它会在蒙版上附加一个新圆圈。
但是,有些项目有一个较小的圆圈图标覆盖,像这样:
我在上面的方法中添加了第二个CGRect
参数,来有条件地切出这个圆圈。但是,遮罩在两个圆圈重叠的地方保持完好,如下所示:
我在这里查看了一些答案,因为我需要找到一种方法来获得两个 UIBezierPath
对象的联合。然而,这被证明是非常困难的。我不认为我可以使用绘图上下文,因为这是一个 UIEffectView
并且需要迭代切割遮罩。
我尝试更改填充规则(.evenOdd
、.nonZero
),但这并没有达到预期的效果。
有什么技巧可以将两个重叠 UIBezierPath
组合成一个蒙版吗?
总体目标是通过连续的表格视图单元格实现这种效果,但有些图标会有额外的圆圈。
请注意底部图标如何有多余的圆圈,但它被裁剪了,我目前剪掉这个多余圆圈的技术导致了上面提到的问题,即重叠部分没有按预期被遮盖。
在您的函数中尝试以下代码
let newRect: CGRect
if let rect2 = rect2{
let raw = rect1.union(rect2)
let size = max(raw.width, raw.height)
newRect = CGRect(x: raw.minX, y: raw.minX, width: size, height: size)
}else{
newRect = rect1
}
let path1 = UIBezierPath(ovalIn: newRect)
newClipPath.append(path1)
全功能
func cutCircle(inView view: UIView, rect1: CGRect, rect2: CGRect?) {
// Create new path and mask
let newMask = CAShapeLayer()
// Create path to clip
let newClipPath = UIBezierPath(rect: view.bounds)
let newRect: CGRect
if let rect2 = rect2{
let raw = rect1.union(rect2)
let size = max(raw.width, raw.height) // getting the larger value in order to draw a proper circle
newRect = CGRect(x: raw.minX, y: raw.minX, width: size, height: size)
}else{
newRect = rect1
}
let path1 = UIBezierPath(ovalIn: newRect)
newClipPath.append(path1)
// If view already has a mask
if let originalMask = view.layer.mask, let originalShape = originalMask as? CAShapeLayer, let originalPath = originalShape.path {
// Create bezierpath from original mask's path
let originalBezierPath = UIBezierPath(cgPath: originalPath)
// Append view's bounds to "reset" the mask path before we re-apply the original
newClipPath.append(UIBezierPath(rect: view.bounds))
// Combine new and original paths
newClipPath.append(originalBezierPath)
}
// Apply new mask
newMask.path = newClipPath.cgPath
newMask.fillRule = .evenOdd
view.layer.mask = newMask
}
我使用内置函数 union 创建了一个原始的 CGRect,然后我得到了最大值来画一个合适的圆。
您可以使用 addArcWithCenter 方法将两条弧组合成所需的 shape.eg:
#define DEGREES_TO_RADIANS(degrees)((M_PI * degrees)/180)
- (UIBezierPath*)createPath
{
UIBezierPath* path = [[UIBezierPath alloc]init];
[path addArcWithCenter:CGPointMake(25, 25) radius:25 startAngle:DEGREES_TO_RADIANS(30) endAngle:DEGREES_TO_RADIANS(60) clockwise:false];
[path addArcWithCenter:CGPointMake(40, 40) radius:10 startAngle:DEGREES_TO_RADIANS(330) endAngle:DEGREES_TO_RADIANS(120) clockwise:true];
[path closePath];
return path;
}
我无法找到确切的点,但如果您可以调整常量,您可以创建所需的完美形状。
感谢接受的答案(来自用户 Tibin Thomas),我能够使用 UIBezierPath
调整弧的使用以获得我所需要的。我接受了他的回答,但在这里发布了我的最终代码以供将来参考。
值得注意的是,在调用此方法之前,我必须将 CGRect
坐标从 UIImageView
s 超级视图转换为我的 UIEffectView
的坐标 space。我还应用了 -1 的插图来获得 1pt 边框。因此,使用的半径比我的 UIImageView
s.
的半径大 1
func cutCircle(inView view: UIView, rect1: CGRect, rect2: CGRect?) {
// Create new path and mask
let newMask = CAShapeLayer()
// Create path to clip
let newClipPath = UIBezierPath(rect: view.bounds)
// Center of rect1
let x1 = rect1.midX
let y1 = rect1.midY
let center1 = CGPoint(x: x1, y: y1)
// New path
let newPath: UIBezierPath
if let rect2 = rect2 {
// Need to get two arcs - main icon and padlock icon
// Center of rect2
let x2 = rect2.midX
let y2 = rect2.midY
let center2 = CGPoint(x: x2, y: y2)
// These values are hard-coded for 25pt radius main icon and bottom-right-aligned 10pt radius padlock icon with a 1pt additional border
newPath = UIBezierPath(arcCenter: center1, radius: 26, startAngle: 1.2, endAngle: 0.3, clockwise: true)
newPath.addArc(withCenter: center2, radius: 11, startAngle: 5.8, endAngle: 2.2, clockwise: true)
} else {
// Only single circle is needed
newPath = UIBezierPath(ovalIn: rect1)
}
newPath.close()
newClipPath.append(newPath)
// If view already has a mask
if let originalMask = view.layer.mask,
let originalShape = originalMask as? CAShapeLayer,
let originalPath = originalShape.path {
// Create bezierpath from original mask's path
let originalBezierPath = UIBezierPath(cgPath: originalPath)
// Append view's bounds to "reset" the mask path before we re-apply the original
newClipPath.append(UIBezierPath(rect: view.bounds))
// Combine new and original paths
newClipPath.append(originalBezierPath)
}
// Apply new mask
newMask.path = newClipPath.cgPath
newMask.fillRule = .evenOdd
view.layer.mask = newMask
}
我想在 tableView 上应用带有模糊效果的 UIEffectView
,但每个单元格中的圆形 UIImageView
对象都显示出来。我使用了一个掩码并改编了
func cutCircle(inView view: UIView, rect1: CGRect, rect2: CGRect?) {
// Create new path and mask
let newMask = CAShapeLayer()
// Create path to clip
let newClipPath = UIBezierPath(rect: view.bounds)
let path1 = UIBezierPath(ovalIn: rect1)
newClipPath.append(path1)
if let rect2 = rect2 {
let path2 = UIBezierPath(ovalIn: rect2)
//FIXME: Need a way to get a union of both paths!
//newClipPath.append(path2)
}
// If view already has a mask
if let originalMask = view.layer.mask, let originalShape = originalMask as? CAShapeLayer, let originalPath = originalShape.path {
// Create bezierpath from original mask's path
let originalBezierPath = UIBezierPath(cgPath: originalPath)
// Append view's bounds to "reset" the mask path before we re-apply the original
newClipPath.append(UIBezierPath(rect: view.bounds))
// Combine new and original paths
newClipPath.append(originalBezierPath)
}
// Apply new mask
newMask.path = newClipPath.cgPath
newMask.fillRule = .evenOdd
view.layer.mask = newMask
}
此函数在 UIEffectView
上为每个可见的表格视图单元格调用,使用:for cell in tableView.visibleCells()
。它会在蒙版上附加一个新圆圈。
但是,有些项目有一个较小的圆圈图标覆盖,像这样:
我在上面的方法中添加了第二个CGRect
参数,来有条件地切出这个圆圈。但是,遮罩在两个圆圈重叠的地方保持完好,如下所示:
我在这里查看了一些答案,因为我需要找到一种方法来获得两个 UIBezierPath
对象的联合。然而,这被证明是非常困难的。我不认为我可以使用绘图上下文,因为这是一个 UIEffectView
并且需要迭代切割遮罩。
我尝试更改填充规则(.evenOdd
、.nonZero
),但这并没有达到预期的效果。
有什么技巧可以将两个重叠 UIBezierPath
组合成一个蒙版吗?
总体目标是通过连续的表格视图单元格实现这种效果,但有些图标会有额外的圆圈。
请注意底部图标如何有多余的圆圈,但它被裁剪了,我目前剪掉这个多余圆圈的技术导致了上面提到的问题,即重叠部分没有按预期被遮盖。
在您的函数中尝试以下代码
let newRect: CGRect
if let rect2 = rect2{
let raw = rect1.union(rect2)
let size = max(raw.width, raw.height)
newRect = CGRect(x: raw.minX, y: raw.minX, width: size, height: size)
}else{
newRect = rect1
}
let path1 = UIBezierPath(ovalIn: newRect)
newClipPath.append(path1)
全功能
func cutCircle(inView view: UIView, rect1: CGRect, rect2: CGRect?) {
// Create new path and mask
let newMask = CAShapeLayer()
// Create path to clip
let newClipPath = UIBezierPath(rect: view.bounds)
let newRect: CGRect
if let rect2 = rect2{
let raw = rect1.union(rect2)
let size = max(raw.width, raw.height) // getting the larger value in order to draw a proper circle
newRect = CGRect(x: raw.minX, y: raw.minX, width: size, height: size)
}else{
newRect = rect1
}
let path1 = UIBezierPath(ovalIn: newRect)
newClipPath.append(path1)
// If view already has a mask
if let originalMask = view.layer.mask, let originalShape = originalMask as? CAShapeLayer, let originalPath = originalShape.path {
// Create bezierpath from original mask's path
let originalBezierPath = UIBezierPath(cgPath: originalPath)
// Append view's bounds to "reset" the mask path before we re-apply the original
newClipPath.append(UIBezierPath(rect: view.bounds))
// Combine new and original paths
newClipPath.append(originalBezierPath)
}
// Apply new mask
newMask.path = newClipPath.cgPath
newMask.fillRule = .evenOdd
view.layer.mask = newMask
}
我使用内置函数 union 创建了一个原始的 CGRect,然后我得到了最大值来画一个合适的圆。
您可以使用 addArcWithCenter 方法将两条弧组合成所需的 shape.eg:
#define DEGREES_TO_RADIANS(degrees)((M_PI * degrees)/180)
- (UIBezierPath*)createPath
{
UIBezierPath* path = [[UIBezierPath alloc]init];
[path addArcWithCenter:CGPointMake(25, 25) radius:25 startAngle:DEGREES_TO_RADIANS(30) endAngle:DEGREES_TO_RADIANS(60) clockwise:false];
[path addArcWithCenter:CGPointMake(40, 40) radius:10 startAngle:DEGREES_TO_RADIANS(330) endAngle:DEGREES_TO_RADIANS(120) clockwise:true];
[path closePath];
return path;
}
我无法找到确切的点,但如果您可以调整常量,您可以创建所需的完美形状。
感谢接受的答案(来自用户 Tibin Thomas),我能够使用 UIBezierPath
调整弧的使用以获得我所需要的。我接受了他的回答,但在这里发布了我的最终代码以供将来参考。
值得注意的是,在调用此方法之前,我必须将 CGRect
坐标从 UIImageView
s 超级视图转换为我的 UIEffectView
的坐标 space。我还应用了 -1 的插图来获得 1pt 边框。因此,使用的半径比我的 UIImageView
s.
func cutCircle(inView view: UIView, rect1: CGRect, rect2: CGRect?) {
// Create new path and mask
let newMask = CAShapeLayer()
// Create path to clip
let newClipPath = UIBezierPath(rect: view.bounds)
// Center of rect1
let x1 = rect1.midX
let y1 = rect1.midY
let center1 = CGPoint(x: x1, y: y1)
// New path
let newPath: UIBezierPath
if let rect2 = rect2 {
// Need to get two arcs - main icon and padlock icon
// Center of rect2
let x2 = rect2.midX
let y2 = rect2.midY
let center2 = CGPoint(x: x2, y: y2)
// These values are hard-coded for 25pt radius main icon and bottom-right-aligned 10pt radius padlock icon with a 1pt additional border
newPath = UIBezierPath(arcCenter: center1, radius: 26, startAngle: 1.2, endAngle: 0.3, clockwise: true)
newPath.addArc(withCenter: center2, radius: 11, startAngle: 5.8, endAngle: 2.2, clockwise: true)
} else {
// Only single circle is needed
newPath = UIBezierPath(ovalIn: rect1)
}
newPath.close()
newClipPath.append(newPath)
// If view already has a mask
if let originalMask = view.layer.mask,
let originalShape = originalMask as? CAShapeLayer,
let originalPath = originalShape.path {
// Create bezierpath from original mask's path
let originalBezierPath = UIBezierPath(cgPath: originalPath)
// Append view's bounds to "reset" the mask path before we re-apply the original
newClipPath.append(UIBezierPath(rect: view.bounds))
// Combine new and original paths
newClipPath.append(originalBezierPath)
}
// Apply new mask
newMask.path = newClipPath.cgPath
newMask.fillRule = .evenOdd
view.layer.mask = newMask
}