代码之家  ›  专栏  ›  技术社区  ›  Chris

使用相交的UIBezierPath遮罩UIEffect视图

  •  0
  • Chris  · 技术社区  · 7 年前

    我想申请一份工作 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() .它在面具上附加了一个新的圆圈。

    但是,有些项目有一个较小的圆形图标覆盖,如下所示:

    Icon

    我加了第二个 CGRect 上述方法的参数,有条件地切出这个圆。但是,当两个圆重叠时,遮罩保持不变,如下所示:

    Icon with blur effect

    我在这里看到了一些答案,因为我需要找到一种方法来实现两人的结合 UIBezierPath 物体。然而,事实证明这非常困难。我不认为我可以使用绘图上下文,因为这是一个 UIEffectView 口罩需要反复切割。

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

    有什么建议可以把两个重叠的部分结合起来吗 尤比齐尔路径 变成一个面具?


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

    Full effect

    请注意,底部图标有一个额外的圆圈,但它是被裁剪的,而我目前剪切这个额外圆圈的技术导致了上面提到的问题,重叠部分没有像预期的那样被掩盖。

    0 回复  |  直到 7 年前
        1
  •  1
  •   Tibin Thomas    7 年前

    你可以用 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;
    }
    

    我无法找到确切的点,但如果你能调整常数,你就可以创造出所需的完美形状。

        2
  •  1
  •   Sahil Manchanda    7 年前

    在函数中尝试以下代码

     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
  •  1
  •   Chris    7 年前

    感谢(用户)接受的答案 Tibin Thomas )我能够适应弧线的使用 UIBezierPath 为了得到我所需要的。我已经接受了他的回答,但将我的最终代码发布在这里以供将来参考。

    注意,在调用这个方法之前,我必须转换 CGRect 坐标来自 UIImageView s superview到我的 UIEffectView .我还申请了-1的插图以获得1pt边界。因此,使用的半径比我的半径大1 UIImageView s

    Masked icons

    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
    }