【问题标题】:Masking a UIEffectView with intersecting UIBezierPath用相交的 UIBezierPath 屏蔽 UIEffectView
【发布时间】:2023-04-09 02:37:01
【问题描述】:

我想在 tableView 上应用带有模糊的UIEffectView,但每个单元格中都有圆形的UIImageView 对象显示出来。我使用了一个蒙版并调整了this answer 的解决方案,以创建一种可以迭代地在每个单元格上方切割出圆圈的方法:

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 上为每个可见的 tableview 单元格调用此函数:for cell in tableView.visibleCells()。它会在掩码上附加一个新圆圈。

但是,有些项目有一个较小的圆圈图标叠加层,如下所示:

我在上面的方法中添加了第二个CGRect参数来有条件地剪掉这个圆圈。但是,蒙版在两个圆圈重叠的地方保持不变,如下所示:

我在这里查看了一些答案,因为我需要找到一种方法来合并两个 UIBezierPath 对象。然而,事实证明这非常困难。我不认为我可以使用绘图上下文,因为这是 UIEffectView 并且需要迭代地剪切蒙版。

我尝试更改填充规则(.evenOdd.nonZero),但这并没有达到预期的效果。

有没有什么技巧可以将两个重叠的UIBezierPath 组合成一个蒙版?


总体目标是通过连续的tableview单元格来实现这种效果,但是有些图标会有额外的圆圈。

注意底部图标有多余的圆圈,但它被裁剪了,而我目前剪掉这个多余圆圈的技术会导致上面提到的问题,其中重叠没有按预期屏蔽。

【问题讨论】:

  • 这是一段视频,展示了效果的当前状态并演示了底部图标第二个圆圈的问题:dropbox.com/s/lvjhf6okgr6n3vn/IMG_3903.TRIM.MOV?raw=1
  • 您不能使用 addArcWithCenter 方法绘制两条弧线并将它们组合成一个 UIBezierpath 吗?
  • @TibinThomas 谢谢你。几分钟前我刚刚遇到了弧贝塞尔路径方法,圆圈的交点很容易找到。我将如何使用这种方法?我需要知道交点外两个圆的弧角吗?
  • 是的,您需要两个圆的确切半径以及两个圆的确切起点和终点角度,以便两条合并的弧形成您想要的单一形状。如果圆的大小恒定这可能很容易计算。
  • @TibinThomas 是的,圆圈是不变的。较大的一个半径为 25pt,较小的一个半径为 10pt,并与包围较大圆圈的正方形的右下角对齐。您能否提交一个链接两个弧的演示,因为我认为这可能是我正在寻找的。​​span>

标签: ios swift uibezierpath uiblureffect


【解决方案1】:

您可以使用 addArcWithCenter 方法将两条弧线组合成所需的形状。例如:

 #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;
}

我无法找到确切的点,但如果您可以调整常数,您可以创建所需的完美形状。

【讨论】:

  • 非常感谢。我稍后会测试它。这些点是否必须在我的 imageView 的直接超级视图的上下文中?
  • 另外,我想我可以很容易地将其转换为 Swift,但是您知道 Swift 与这些方法有什么不同吗?
  • 您可以轻松转换为 swift,因为没有区别。但我不确定您应该使用的点的上下文。我的假设是您应该使用效果视图框架的上下文。
  • 没问题。鉴于我已经绘制了贝塞尔曲线,相同的上下文应该可以工作。谢谢
  • 请参考@chris 回答以获得完美的解决方案。
【解决方案2】:

在您的函数中尝试以下代码

 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,然后我得到最大值来绘制一个合适的圆。

【讨论】:

  • 谢谢你-我稍后再试试
  • 这将在两个圆形图标周围绘制一个圆圈(即,在正方形内的一个圆圈将它们都包围起来)。目前,我的较小的可选图标始终包含在较大的“正方形”中,与右下角对齐。
【解决方案3】:

感谢接受的答案(来自用户Tibin Thomas),我能够通过UIBezierPath 调整弧的使用,以获得我所需要的。我接受了他的回答,但在此处发布了我的最终代码以供将来参考。

值得注意的是,在调用此方法之前,我必须将CGRect 坐标从UIImageViews 超视图转换为我的UIEffectView 的坐标空间。我还应用了 -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
}

【讨论】:

  • 干得好!你应该分享功劳,因为我只提供了一个建议,你完善了它。!
  • @TibinThomas 谢谢,但如果没有你的回答,我就无法到达那里,而且我不能给自己赏金:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-28
  • 1970-01-01
  • 1970-01-01
  • 2015-09-05
  • 2017-07-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多