使用相交的 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 坐标从 UIImageViews 超级视图转换为我的 UIEffectView 的坐标 space。我还应用了 -1 的插图来获得 1pt 边框。因此,使用的半径比我的 UIImageViews.

的半径大 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
}