【问题标题】:How to subtract the intersection of UIBezierPath A&B from A?如何从 A 中减去 UIBezierPath A&B 的交集?
【发布时间】:2018-12-15 06:46:34
【问题描述】:

假设我们有两个 UIBezierPaths,path1 和 path2...(已经在运行时相对于视图边界进行了定义,并且已经作为同一视图的属性存在)。

我们要得到一个UIBezierPath类型的新路径,path3,也就是path1减去path2的结果:

这样做的方式(如here所见)是这样做的:

path1.append(path2.reversing())

但是,这似乎只适用于 path1 完全包含 path2 的情况。

例如,考虑只有部分交叉点的情况——路径 1 不完全包含路径 2。如果我们应用与上面相同的方法会发生这种情况:

在 Android 中,答案是:

path1.op(path2, Path.Op.DIFFERENCE);

那么……在IOS有没有等价的简单操作?

如果没有,是否有一个函数可以写成:

func returnPath2CutOutOfPath1(path1: UIBezierPath, path2: UiBezierPath) -> UIBezierPath {

// the mystery lies within these here parts. :)

}

【问题讨论】:

  • 请贴出你试过的代码。
  • 是否足以填补差异,还是出于其他原因需要生成的 UIBezierPath?
  • 理想情况下,就好像第二条路径实际上是从第一条路径中删除的......那样,应用轮廓、背景和轮廓宽度等的正常做法仍然可以正确应用到结果路径。
  • 我在发布这篇文章之前读到了这一点……事实上,我从那里学到了我找到的最佳解决方案:“path1.append(path2.reversing())”

标签: ios swift vector drawing uibezierpath


【解决方案1】:

没有直接的方法可以将 UIBezierPaths 的差异作为 iOS 上的新路径。

测试用例

private func path2() -> UIBezierPath {
    return UIBezierPath(rect: CGRect(x: 100, y: 50, width: 200, height: 200))
}

private func path1() -> UIBezierPath {
    return UIBezierPath(rect: CGRect(x: 50, y: 100, width: 200, height: 200))
}

起点:简单地展示哪条路径代表什么:路径1是黄色,路径2是绿色:

可能性 1

您可能已经看到了这种可能性:在您在问题中提到的链接中,如果您只需要执行填充操作,还有一个聪明的解决方案。

代码取自这个答案(来自您发布的链接)https://stackoverflow.com/a/8860341 - 仅转换为 Swift:

func fillDifference(path2: UIBezierPath, path1: UIBezierPath) {
    let clipPath = UIBezierPath.init(rect: .infinite)
    clipPath.append(path2)
    clipPath.usesEvenOddFillRule = true

    UIGraphicsGetCurrentContext()?.saveGState()
    clipPath.addClip()
    path1.fill()
    UIGraphicsGetCurrentContext()?.restoreGState()
}

所以这填充了一个路径,但没有返回一个UIBezierPath,这意味着你不能应用轮廓、背景、轮廓宽度等,因为结果不是一个UIBezierPath。

看起来像这样:

可能性2

您可以使用第三方库,例如来自一位名叫 Adam Wulf 的作者:https://github.com/adamwulf/ClippingBezier

该库是用 Objective-C 编写的,但可以从 Swift 中调用。

在 Swift 中,它看起来像这样:

override func draw(_ rect: CGRect) {
    let result = self.path1().difference(with: self.path2())

    for p in result ?? [] {
        p.stroke()
    }
}

如果要使用这个库,需要注意一个小提示:在项目设置中的Other Linker Flags中,如readme所述,必须加上“-ObjC++ -lstdc++”,否则编译时没有投诉,但会默默地不加载框架并最终崩溃,因为找不到 UIBEzierPath 类别。

结果如下:

所以这实际上会给出您想要的结果,但您必须使用第 3 方库。

【讨论】:

    【解决方案2】:

    我已经实现了您想要的解决方案,但是我已经为大小为 200x200 的静态视图创建了代码。

    让我向您展示我的尝试,并告诉我对您有多大用处。

    首先,我创建了自定义视图类,我在其中编写了代码来绘制视图。见以下代码:

    class MyCustomView: UIView {
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setup()
        }
    
        func setup() {
    
            // Create a CAShapeLayer
            let shapeLayer = CAShapeLayer()
    
            let path1 = self.path1()
            let path2 = self.path2()
    
            // Append path2 to path1
            path1.append(path2.reversing())
    
            // Set true of Even Odd Fill Rule
            path1.usesEvenOddFillRule = true
    
            // Call this method to add clip in path
            path1.addClip()
    
            shapeLayer.path = path1.cgPath
    
            // Apply other properties related to the path
            shapeLayer.strokeColor = UIColor.blue.cgColor
            shapeLayer.fillColor = UIColor.black.cgColor
            shapeLayer.lineWidth = 1.0
            shapeLayer.position = CGPoint(x: 0, y: 0)
    
            // Add the new layer to our custom view
            self.layer.addSublayer(shapeLayer)
        }
    
        //-----------------------------------------------------
        // This is static code as I have already told you first.
        // Create First UIBezierPath,
    
        func path1() -> UIBezierPath {
            let path = UIBezierPath()
            path.move(to: CGPoint(x: 0, y: 0))
            path.addLine(to: CGPoint(x: 0, y: 200))
            path.addLine(to: CGPoint(x: 200, y: 200))
            path.addLine(to: CGPoint(x: 200, y: 0))
            path.close()
            return path
        }
    
        // Create Second UIBezierPath
        func path2() -> UIBezierPath {
            let path = UIBezierPath()
            path.move(to: CGPoint(x: 50, y: -50))
            path.addLine(to: CGPoint(x: 50, y: 150))
            path.addLine(to: CGPoint(x: 250, y: 150))
            path.addLine(to: CGPoint(x: 250, y: -50))
            path.close()
            return path
        }
    }
    

    此自定义类代码将使用您在图 4 中描述的实际结果创建共享层。

    现在要使用这个类,我已经创建了具有固定高度和宽度的上述类的实例。

    override func viewDidLoad() {
        super.viewDidLoad()
    
        // Create a new UIView and add it to the view controller
        let myView = MyCustomView()
    
        // Must set clipsToBounds true to remove extra layer which are display out side of view as like your **actual** result in figure 4.
        myView.clipsToBounds = true 
        myView.frame = CGRect(x: 50, y: 100, width: 200, height: 200)
        myView.backgroundColor = UIColor.orange
        view.addSubview(myView)
    }
    

    这将显示如下视图,这是您想要的结果。

    希望对您有所帮助,如果您有任何疑问,请告诉我。

    【讨论】:

    • 感谢您的努力!我可能会从中学到一两件事,但我写了一个不够精确的糟糕问题。我做了一些编辑。我很抱歉。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-10
    • 1970-01-01
    • 2022-11-03
    • 2011-11-06
    • 1970-01-01
    • 2011-03-28
    • 1970-01-01
    相关资源
    最近更新 更多