CAShapeLayer. Does the line pass through the point?

戏子无情 提交于 2021-01-28 05:12:29

问题


I use CAShapeLayer in order to draw a line on the screen. In the method touchesEnded I want to check " Does the line pass through the point?". In my code when I press on the any part of the screen the method contains returns always true. Perhaps, I have problem in line.frame = (view?.bounds)!. How can I fix it? Sorry for my bad English.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    let touch = touches.first
    let firstPosition = touch?.location(in: self)

    if atPoint(firstPosition!) == lvl1 {

        let firstPositionX = firstPosition?.x
        let firstPositionY = frame.size.height - (firstPosition?.y)!
        view?.layer.addSublayer(line)
        line.lineWidth = 8
        let color = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1).cgColor
        line.strokeColor = color
        line.fillColor = nil
        line.frame = (view?.bounds)!
        path.move(to: CGPoint(x: firstPositionX!, y: firstPositionY))

    }

}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

    let touch = touches.first
    let firstPosition = touch?.location(in: self)

    if atPoint(firstPosition!) == lvl1 {

        let firstPositionX = firstPosition?.x
        let firstPositionY = frame.size.height - (firstPosition?.y)!
        path.addLine(to: CGPoint(x: firstPositionX!, y: firstPositionY))
        line.path = path.cgPath

    }
}


override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

    if line.contains(screenCenterPoint) {
        print("ok")
    }

}

回答1:


Problem

The method func contains(_ p: CGPoint) -> Bool of CAShapeLayer returns true if the bounds of the layer contains the point. (see documentation)

So you cannot use this to check if the line contains a point.

There is however, another method with the same name in the class CGPath that returns whether the specified point is interior to the path. But since you only stroke your path and you don't fill the interior, this method will not give the desired result either.

Solution

The trick is to create an outline of your path using:

let outline = path.cgPath.copy(strokingWithWidth: line.lineWidth, lineCap: .butt, lineJoin: .round, miterLimit: 0)

And then check if the interior of the outline contains your screenCenterPoint

if outline.contains(screenCenterPoint) {
    print("ok")
}

Performance considerations

Since you are checking the containment only when touches end, I think that creating an outline of the path does not add too much overhead.

When you want to check the containment in realtime, for example inside the touchesMoved function, calculating an outline may produce some overhead because this method is called a lot of times per second. Also the longer the path becomes, the longer it will take to calculate the outline.

So in realtime it is better to generate only the outline of the last drawn segment and then check if that outline contains your point.

If you want to reduce overhead seriously, you can write your own containment function. Containment of a point in a straight line is fairly simple and can be reduced to the following formula:

Given a line from start to end with width and a point p

Calculate:

  • dx = start.x - end.x
  • dy = start.y - end.y
  • a = dy * p.x - dx * p.y + end.x * start.y - end.y * start.x
  • b = hypot(dy, dx)

The line contains point p if:

abs(a/b) < width/2 and p is in the bounding box of the line.



来源:https://stackoverflow.com/questions/45532330/cashapelayer-does-the-line-pass-through-the-point

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!