I have array of CGPoint
indicated in red. I must connect all consecutive points with CGRect
as shown in the rough figure to form an array of CGRect
. Consider all rect has a constant width and each point could be the mid point of the sides formed from width of the rect. How could I form this array of CGRect
?
EDIT:
Here is what I am trying to do. As described in my previous question, I am trying to introduce an eraser function.
I have a set of straight lines (in green). I basically contain the start and end points of the line in model. When in eraser mode, user is allowed to draw freely and all draw points are connected by stroke (in purple).
When any green line is completely covered, the line must be identified to be erased. But, I couldn’t able to determine if the line is completed covered by the eraser’s draw points.
As in comments, I tried to follow the answer here. All I have is CGPoint
s and I have no CGRect
s. I check the intersection of both green and purple path as below and it returns false
.
public override func draw(_ rect: CGRect) {
let wallPath = UIBezierPath()
wallPath.move(to: CGPoint(x: 50.0, y: 50.0))
wallPath.addLine(to: CGPoint(x: 50.0, y: 400.0))
wallPath.addLine(to: CGPoint(x: 300.0, y: 400.0))
wallPath.addLine(to: CGPoint(x: 300.0, y: 50.0))
let wallLayer = CAShapeLayer()
wallLayer.path = wallPath.cgPath
wallLayer.lineWidth = 10
wallLayer.strokeColor = UIColor.green.cgColor
wallLayer.fillColor = nil
layer.addSublayer(wallLayer)
let eraserPath = UIBezierPath()
eraserPath.move(to: CGPoint(x: 40.0, y: 75.0))
eraserPath.addLine(to: CGPoint(x: 120.0, y: 75.0))
let eraserLayer = CAShapeLayer()
eraserLayer.path = eraserPath.cgPath
eraserLayer.lineWidth = 15
eraserLayer.strokeColor = UIColor.purple.cgColor
layer.addSublayer(eraserLayer)
if wallPath.cgPath.intersects(eraserPath.cgPath) {
print("Overlapping")
} else {
print("Not overlapping")
}
}
To make it more clear about the requirement, when user draws in eraser mode, based on the draw points, I have to identify which green line falls completely in the purple stroke’s coverage and the identified green line must be taken for further processing. Multiple green lines could be selected at the same time.
2
Answers
So you have two
CGPaths
, one for the eraser and one for a line. You stroke those paths with a certain line width, which creates two areas. You want to check if one area completely covers another.Let’s first not worry about "completely" – just check if the two areas intersect at any point at all. You can do this by creating new
CGPath
s that represent the area created when stroking with a certain line width. There is an API this –copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:)
.Then you can just use
intersect
on those paths.Here is an idea of how that would look:
Determining if these areas completely intersect is not trivial at all. And I think it would be a better UX if you allow a line to be erased just by intersection, and not completely covering.
If you really want though, here is an approximation you could do. Divide the line into small "pieces", and for each of those "piece", check if the area of that intersects with the eraser area.
Here is an idea of how that would look:
Note that I’m calling
eraserIntersectsLine
every time here for convenience, which creates the same path for the eraser area every time. It should be trivial to optimise this so that it only creates one copy of the eraser area if you need to.For a more accurate approximation, you could consider the width of the line too. Split the line width into small pieces too.
CGPath
has an.lineIntersection(_:using:)
method that can come in really handy here…Let’s start with a single line path:
We create an "eraser" path – using
strokingWithWidth
instead of.lineWidth
so we get a "filled" outline path – and cross the path:When we call:
That returns a new path, consisting of
.move(to: pt1)
and.addLine(to: pt2)
– if we draw thatiPth
in cyan we see:We can then easily compare the resulting path to the original path and determine that the original is not completely encompassed.
If we continue defining our eraser path:
and call
segPth.lineIntersection(eraserPth)
again, it will return a path ofmove to
+line to
+move to
+line to
:and again, we can easily determine that it is not the same path as the original line segment.
If we keep adding points to the eraser path until we get this:
segPth.lineIntersection(eraserPth)
will now return a path:That matches the original.
So… a couple notes first…
Define your "walls" path as a series of "line segments" rather than a single path:
That makes it easy to:
.lineIntersection()
Next, because
points
use floating-point values, and UIKit likes whole numbers, we can save ourselves some headaches byrounding
everything.For example, if we have a line from
20, 10
to80, 10
, and we have an eraser path that encompasses the entire line path, we might get a returned path from.lineIntersection()
with points like20.00001, 10.0
to79.99997, 10.000001
.Here is a complete example to play with…
The
LineSegment
struct:extension
to round the x,y values of a point:This
extension
to return the points of a path (found here: How to get the CGPoint(s) of a CGPath):A
UIView
subclass, with shape layers, path logic, and touch handling:and a simple view controller to show it in use:
Here’s what it looks like when running: