问题
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
toend
withwidth
and a pointp
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