I have an app in which you can draw using points, the code presented below is needed so that when one line (already drawn) intersects with current, the current line is deleted.
When I try to do this in DragGesture().onChanged
, the CPU is overloaded and the lines are drawn with a delay.
How to fix that?
import SwiftUI
import CoreGraphics
struct Drawing1 {
var points1: [CGPoint] = [CGPoint]()
}
struct Drawing2 {
var points2: [CGPoint] = [CGPoint]()
}
struct ContentView: View {
@State private var currentDrawing1: Drawing1 = Drawing1()
@State private var drawings1: [Drawing1] = [Drawing1]()
@State private var currentDrawing2: Drawing2 = Drawing2()
@State private var drawings2: [Drawing2] = [Drawing2]()
var body: some View {
GeometryReader { geo in
Path { path1 in
for drawings1 in self.drawings1 {
self.add1(drawings1: drawings1, toPath1: &path1)
}
self.add1(drawings1: self.currentDrawing1, toPath1: &path1)
}
.stroke(Color.orange, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
Path { path2 in
for drawings2 in self.drawings2 {
self.add2(drawings2: drawings2, toPath2: &path2)
}
self.add2(drawings2: self.currentDrawing2, toPath2: &path2)
}
.stroke(Color.black, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
Image("example1")
.resizable( resizingMode: .stretch)
.scaledToFit()
.clipped()
.position(x:200, y:200)
.frame(width: 100, height:100)
.gesture(
DragGesture()
.onChanged({ value1 in
let currentPoint1 = value1.location
self.currentDrawing1.points1.append(currentPoint1)
} )
.onEnded({ value1 in
self.drawings1.append(self.currentDrawing1)
self.currentDrawing1 = Drawing1()
}
)
)
Image("example2")
.resizable( resizingMode: .stretch)
.scaledToFit()
.clipped()
.position(x:100, y:100)
.frame(width: 100, height:100)
.gesture(
DragGesture()
.onChanged({ value2 in
let currentPoint2 = value2.location
self.currentDrawing2.points2.append(currentPoint2)
if drawings1.count > 0
{
let points2 = currentDrawing2.points2
let points1 = drawings1[0].points1
if points2.count > 0
{
for i in 0..<points2.count-1 {
let current2 = points2[i]
let next2 = points2[i+1]
for i in 0..<points1.count-1 {
let current = points1[i]
let next = points1[i+1]
let delta1x = next.x - current.x
let delta1y = next.y - current.y
let delta2x = next2.x - current2.x
let delta2y = next2.y - current2.y
// create a 2D matrix from our vectors and calculate the determinant
let determinant = delta1x * delta2y - delta2x * delta1y
if abs(determinant) < 0.0001 {
// if the determinant is effectively zero then the lines are parallel/colinear
//return nil
}
// if the coefficients both lie between 0 and 1 then we have an intersection
let ab = ((current.y - current2.y) * delta2x - (current.x - current2.x) * delta2y) / determinant
if ab > 0 && ab < 1 {
let cd = ((current.y - current2.y) * delta1x - (current.x - current2.x) * delta1y) / determinant
if cd > 0 && cd < 1 {
// lines cross
self.currentDrawing2.points2.removeAll()
}
}
}
}
}
}
}
)
.onEnded( { value2 in
self.drawings2.append(self.currentDrawing2)
self.currentDrawing2 = Drawing2()
})
)
}
}
private func add1(drawings1: Drawing1, toPath1 path1: inout Path) {
let points1 = drawings1.points1
if points1.count > 1 {
for i in 0..<points1.count-1 {
let current = points1[i]
let next = points1[i+1]
path1.move(to: current)
path1.addLine(to: next)
}
}
}
private func add2(drawings2: Drawing2, toPath2 path2: inout Path) {
let points2 = drawings2.points2
if points2.count > 1 {
for i in 0..<points2.count-1 {
let current = points2[i]
let next = points2[i+1]
path2.move(to: current)
path2.addLine(to: next)
}
}
}
}
I tried to implement this in .onEnded
. Everything works fine here, but I need it to work in real time.
We can detect intersecting paths with lineIntersection(_:eoFill:)
For example, let's create two Paths:
var pth1 = Path()
var pth2 = Path()
pth1.move(to: .init(x: 200.0, y: 300.0))
pth1.addLine(to: .init(x: 150.0, y: 350.0))
pth1.addLine(to: .init(x: 120.0, y: 320.0))
pth1.addLine(to: .init(x: 80.0, y: 250.0))
pth2.move(to: .init(x: 180.0, y: 200.0))
pth2.addLine(to: .init(x: 180.0, y: 240.0))
pth2.addLine(to: .init(x: 140.0, y: 260.0))
pth2.addLine(to: .init(x: 160.0, y: 300.0))
If we draw them, it looks like this:
So, let's check:
let iPath = pth1.lineIntersection(pth2)
// we need to check the .cgPath
if iPath.cgPath.isEmpty {
print("Paths do NOT Intersect")
} else {
print("Paths Intersect!")
}
Debug console output will be: Paths do NOT Intersect
If we add another line segment to pth2
:
pth2.addLine(to: .init(x: 220.0, y: 340.0))
it now looks like this:
and the output will be: Paths Intersect!
So, instead of tracking all of the points while dragging, let's update a Path
:
struct Drawing {
var thePath: Path = Path()
}
and in .onChanged
we can use .move(to:)
and .addLine(to:)
so we're not re-building the path every cycle.
Let's also define two other properties:
struct Drawing {
var thePath: Path = Path()
var theColor: Color = Color.black
var canDraw: Bool = true
}
It's not clear what you're trying to do with your arrays of "drawings" so we'll simplify things a bit:
@State private var currentDrawing1: Drawing = Drawing(theColor: Color.red)
@State private var currentDrawing2: Drawing = Drawing(theColor: Color.blue)
Here's the full view struct that you can examine and try out (should be enough comments):
struct DrawIntersectView: View {
@State private var currentDrawing1: Drawing = Drawing(theColor: Color.red)
@State private var currentDrawing2: Drawing = Drawing(theColor: Color.blue)
var body: some View {
GeometryReader { geo in
currentDrawing1.thePath
.stroke(currentDrawing1.theColor, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
currentDrawing2.thePath
.stroke(currentDrawing2.theColor, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
Image("example1")
.resizable( resizingMode: .stretch)
.scaledToFit()
.clipped()
.position(x:200, y:200)
.frame(width: 100, height:100)
.opacity(0.5) // so we can see the line start
.gesture(
DragGesture()
.onChanged({ value in
let currentPoint = value.location
// if the path has no points yet, move to
// else, addLine to
if self.currentDrawing1.thePath.isEmpty {
self.currentDrawing1.thePath.move(to: currentPoint)
} else {
self.currentDrawing1.thePath.addLine(to: currentPoint)
}
})
.onEnded({ value in
})
)
Image("example2")
.resizable( resizingMode: .stretch)
.scaledToFit()
.clipped()
.position(x:100, y:100)
.frame(width: 100, height:100)
.opacity(0.5) // so we can see the line start
.gesture(
DragGesture()
.onChanged({ value in
// if we haven't yet drawn from currentDrawing2
// just return
if self.currentDrawing1.thePath.isEmpty {
return
}
// if we've crossed the first line, we don't want to
// draw until the user has lifted the touch (ended the current drag)
if !self.currentDrawing2.canDraw {
return
}
let currentPoint = value.location
// if the path has no points yet, move to
// else, addLine to
if self.currentDrawing2.thePath.isEmpty {
self.currentDrawing2.thePath.move(to: currentPoint)
} else {
self.currentDrawing2.thePath.addLine(to: currentPoint)
}
// get lineIntersection of the paths from drawing 1 and drawing 2
let t = self.currentDrawing1.thePath.lineIntersection(self.currentDrawing2.thePath)
if !t.cgPath.isEmpty {
print("intersects")
self.currentDrawing2.canDraw = false
self.currentDrawing2.thePath = Path()
}
})
.onEnded({ value in
// allow drawing again
self.currentDrawing2.canDraw = true
})
)
}
}
}
Worth noting: because you're using a path line width of 10... if we draw the lines / paths like this:
they do NOT intersect! That's because the actual line paths are:
Edit
The above doesn't quite work...
Apple's docs on CGPath
Instance Methods (.lineIntersection
/ .intersection
/ etc) are rather lacking.
It turns out that .lineIntersection
works like this...
Two non-intersecting paths:
"under-the-hood" the paths get closed:
if we fill them:
.intersection
gets this path (yellow):
and .lineIntersection
returns the portion of the line from that intersection (yellow again):
So that won't work.
To "fix" that, we can use
copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:)
So, if we do:
let sp1 = pth1.copy(strokingWithWidth: 10.0, lineCap: .round, lineJoin: .round, miterLimit: 1.0)
on this path:
we get a closed path (fill red):
Now we can generate two "stroked paths" and check for intersection:
let sp1 = self.currentDrawing1.thePath.cgPath.copy(strokingWithWidth: 10.0, lineCap: .round, lineJoin: .round, miterLimit: 1.0)
let sp2 = self.currentDrawing2.thePath.cgPath.copy(strokingWithWidth: 10.0, lineCap: .round, lineJoin: .round, miterLimit: 1.0)
if sp1.intersects(sp2)
print("intersects")
// remove the 2nd path, etc
}
So, modified example:
struct Drawing {
var thePath: Path = Path()
var sPath: CGPath? // "stroked" path
var theColor: Color = Color.black
var canDraw: Bool = true
}
// stroking with intersection -- iOS 16+
struct DrawIntersectView: View {
@State private var currentDrawing1: Drawing = Drawing(theColor: Color.red)
@State private var currentDrawing2: Drawing = Drawing(theColor: Color.blue)
private var lineWidth: CGFloat = 10.0
var body: some View {
GeometryReader { geo in
currentDrawing1.thePath
.stroke(currentDrawing1.theColor, style: StrokeStyle(lineWidth: self.lineWidth, lineCap: .round, lineJoin: .round))
currentDrawing2.thePath
.stroke(currentDrawing2.theColor, style: StrokeStyle(lineWidth: self.lineWidth, lineCap: .round, lineJoin: .round))
Image("example1")
.resizable( resizingMode: .stretch)
.scaledToFit()
.clipped()
.position(x:200, y:200)
.frame(width: 100, height:100)
.opacity(0.5) // so we can see the line start
.gesture(
DragGesture()
.onChanged({ value in
let currentPoint = value.location
// if the path has no points yet, move to
// else, addLine to
if self.currentDrawing1.thePath.isEmpty {
self.currentDrawing1.thePath.move(to: currentPoint)
} else {
self.currentDrawing1.thePath.addLine(to: currentPoint)
}
})
.onEnded({ value in
self.currentDrawing1.sPath = self.currentDrawing1.thePath.cgPath.copy(strokingWithWidth: self.lineWidth, lineCap: .round, lineJoin: .round, miterLimit: 1.0)
})
)
Image("example2")
.resizable( resizingMode: .stretch)
.scaledToFit()
.clipped()
.position(x:100, y:100)
.frame(width: 100, height:100)
.opacity(0.5) // so we can see the line start
.gesture(
DragGesture()
.onChanged({ value in
// if we haven't yet drawn from currentDrawing2
// just return
if self.currentDrawing1.thePath.isEmpty {
return
}
// if we've crossed the first line, we don't want to
// draw until the user has lifted the touch (ended the current drag)
if !self.currentDrawing2.canDraw {
return
}
let currentPoint = value.location
// if the path has no points yet, move to
// else, addLine to
if self.currentDrawing2.thePath.isEmpty {
self.currentDrawing2.thePath.move(to: currentPoint)
} else {
self.currentDrawing2.thePath.addLine(to: currentPoint)
}
self.currentDrawing2.sPath = self.currentDrawing2.thePath.cgPath.copy(strokingWithWidth: self.lineWidth, lineCap: .round, lineJoin: .round, miterLimit: 1.0)
if let sp1 = self.currentDrawing1.sPath, let sp2 = self.currentDrawing2.sPath {
if sp1.intersects(sp2) {
print("intersects")
self.currentDrawing2.canDraw = false
self.currentDrawing2.thePath = Path()
}
}
})
.onEnded({ value in
// allow drawing again
self.currentDrawing2.canDraw = true
})
)
}
}
}
Edit 2 - comment: "must work with iOS 14
"
Unfortunately, .intersects()
is an iOS 16+
method...
To be iOS 14
compatible, we can still use the "stroked path" approach for "line 1" and then check .contains
for each new point:
// does currentDrawing1's stroked path contain the current point?
if let sp = currentDrawing1.sPath, sp.contains(currentPoint) {
print("intersects")
self.currentDrawing2.canDraw = false
self.currentDrawing2.thePath = Path()
}
// stroking with contains -- iOS 14
struct DrawIntersectView: View {
@State private var currentDrawing1: Drawing = Drawing(theColor: Color.red)
@State private var currentDrawing2: Drawing = Drawing(theColor: Color.blue)
private var lineWidth: CGFloat = 10.0
var body: some View {
GeometryReader { geo in
currentDrawing1.thePath
.stroke(currentDrawing1.theColor, style: StrokeStyle(lineWidth: self.lineWidth, lineCap: .round, lineJoin: .round))
currentDrawing2.thePath
.stroke(currentDrawing2.theColor, style: StrokeStyle(lineWidth: self.lineWidth, lineCap: .round, lineJoin: .round))
Image("example1")
.resizable( resizingMode: .stretch)
.scaledToFit()
.clipped()
.position(x:200, y:200)
.frame(width: 100, height:100)
.opacity(0.5) // so we can see the line start
.gesture(
DragGesture()
.onChanged({ value in
let currentPoint = value.location
// if the path has no points yet, move to
// else, addLine to
if self.currentDrawing1.thePath.isEmpty {
self.currentDrawing1.thePath.move(to: currentPoint)
} else {
self.currentDrawing1.thePath.addLine(to: currentPoint)
}
})
.onEnded({ value in
// update currentDrawing1's "stroked path"
self.currentDrawing1.sPath = self.currentDrawing1.thePath.cgPath.copy(strokingWithWidth: self.lineWidth, lineCap: .round, lineJoin: .round, miterLimit: 1.0)
})
)
Image("example2")
.resizable( resizingMode: .stretch)
.scaledToFit()
.clipped()
.position(x:100, y:100)
.frame(width: 100, height:100)
.opacity(0.5) // so we can see the line start
.gesture(
DragGesture()
.onChanged({ value in
// if we haven't yet drawn from currentDrawing2
// just return
if self.currentDrawing1.thePath.isEmpty {
return
}
// if we've crossed the first line, we don't want to
// draw until the user has lifted the touch (ended the current drag)
if !self.currentDrawing2.canDraw {
return
}
let currentPoint = value.location
// if the path has no points yet, move to
// else, addLine to
if self.currentDrawing2.thePath.isEmpty {
self.currentDrawing2.thePath.move(to: currentPoint)
} else {
self.currentDrawing2.thePath.addLine(to: currentPoint)
}
// does currentDrawing1's stroked path contain the current point?
if let sp = currentDrawing1.sPath, sp.contains(currentPoint) {
print("intersects")
self.currentDrawing2.canDraw = false
self.currentDrawing2.thePath = Path()
}
})
.onEnded({ value in
// allow drawing again
self.currentDrawing2.canDraw = true
})
)
}
}
}
Still have one more thing to deal with...
When dragging quickly, it's pretty easy to get a distance greater-than 10-points between the previous point and the current point:
None of those points is inside the red stroked-path.
To "fix" that...
Approach 1:
.onChanged()
.contains
Approach 2 (if Approach 1 induces lag, which it probably won't):
.onEnded()
.canDraw
is still truePretty easy to find a "points on line with spacing" function out there.
Edit 3
Here is the iOS 14
version, that also handles very-quick-drag:
// stroking with contains and gap handling -- iOS 14
Edit 4 - fixed the "points on line" interpolation
struct Drawing {
var points: [CGPoint] = []
var thePath: Path = Path()
var sPath: CGPath? // "stroked" path
var theColor: Color = Color.black
var canDraw: Bool = true
}
struct ContentView: View {
@State private var currentDrawing1: Drawing = Drawing(theColor: Color.red)
@State private var currentDrawing2: Drawing = Drawing(theColor: Color.blue)
private var lineWidth: CGFloat = 10.0
var body: some View {
GeometryReader { geo in
currentDrawing1.thePath
.stroke(currentDrawing1.theColor, style: StrokeStyle(lineWidth: self.lineWidth, lineCap: .round, lineJoin: .round))
currentDrawing2.thePath
.stroke(currentDrawing2.theColor, style: StrokeStyle(lineWidth: self.lineWidth, lineCap: .round, lineJoin: .round))
Image("example1")
.resizable( resizingMode: .stretch)
.scaledToFit()
.clipped()
.position(x:200, y:200)
.frame(width: 100, height:100)
.opacity(0.5) // so we can see the line start
.gesture(
DragGesture()
.onChanged({ value in
let currentPoint = value.location
self.currentDrawing1.points.append(currentPoint)
// if the path has no points yet, move to
// else, addLine to
if self.currentDrawing1.thePath.isEmpty {
self.currentDrawing1.thePath.move(to: currentPoint)
} else {
self.currentDrawing1.thePath.addLine(to: currentPoint)
}
})
.onEnded({ value in
// update currentDrawing1's "stroked path"
// stroke with lineWidth * 2.0 so we get edge-to-edge detection
self.currentDrawing1.sPath = self.currentDrawing1.thePath.cgPath.copy(strokingWithWidth: self.lineWidth * 2.0, lineCap: .round, lineJoin: .round, miterLimit: 1.0)
})
)
Image("example2")
.resizable( resizingMode: .stretch)
.scaledToFit()
.clipped()
.position(x:100, y:100)
.frame(width: 100, height:100)
.opacity(0.5) // so we can see the line start
.gesture(
DragGesture()
.onChanged({ value in
// if we haven't yet drawn from currentDrawing2
// just return
if self.currentDrawing1.thePath.isEmpty {
return
}
// if we've crossed the first line, we don't want to
// draw until the user has lifted the touch (ended the current drag)
if !self.currentDrawing2.canDraw {
return
}
let currentPoint = value.location
self.currentDrawing2.points.append(currentPoint)
// get the previous point
let prevPoint = self.currentDrawing2.thePath.currentPoint
// if the path has no points yet, move to
// else, addLine to
if self.currentDrawing2.thePath.isEmpty {
self.currentDrawing2.thePath.move(to: currentPoint)
} else {
self.currentDrawing2.thePath.addLine(to: currentPoint)
}
// does currentDrawing1's stroked path contain the current point?
if let sp = currentDrawing1.sPath, sp.contains(currentPoint) {
print("intersects")
self.currentDrawing2.canDraw = false
self.currentDrawing2.thePath = Path()
} else {
// do we have a previous point?
if let pp = prevPoint {
// get the distance from previous to current
let deltaX = pp.x - currentPoint.x
let deltaY = pp.y - currentPoint.y
let length = hypot(deltaX, deltaY)
// if it's greater-than 9.0
if length > 9.0 {
// get the array of points between previous and current, each 8-points apart
let pts = pointsOnLine(start: pp, end: currentPoint)
for p in pts {
// does currentDrawing1's stroked path contain the one of the calculated points?
if let sp = currentDrawing1.sPath, sp.contains(p) {
print("points on line intersects")
self.currentDrawing2.canDraw = false
self.currentDrawing2.thePath = Path()
break
}
}
}
}
}
})
.onEnded({ value in
// allow drawing again
self.currentDrawing2.canDraw = true
})
)
}
}
func pointsOnLine(start: CGPoint, end: CGPoint, spacing: CGFloat = 8.0) -> [CGPoint] {
var points: [CGPoint] = []
// Calculate the differences in x and y
let deltaX = end.x - start.x
let deltaY = end.y - start.y
// Calculate the length of the line
let length = hypot(deltaX, deltaY)
// Normalize the direction of the line to unit length
let unitX = deltaX / length
let unitY = deltaY / length
// Add the first point (start point)
points.append(start)
// Add points at every spacing distance along the line
var currentDistance: CGFloat = spacing
while currentDistance < length {
let x = start.x + unitX * currentDistance
let y = start.y + unitY * currentDistance
points.append(CGPoint(x: x, y: y))
currentDistance += spacing
}
// Add the last point (end point)
points.append(end)
return points
}
}
Edit 5 - see if this makes sense...
Animated GIF - open this image in a new tab/window if the animation is not running...
Here is your code (with your most recent edits), modified to use interpolated points:
struct Drawing1 {
var points1: [CGPoint] = []
}
struct Drawing2 {
var points2: [CGPoint] = []
}
struct ContentView: View {
@State private var currentDrawing1: Drawing1 = Drawing1()
@State private var drawings1: [Drawing1] = [Drawing1]()
@State private var currentDrawing2: Drawing2 = Drawing2()
@State private var drawings2: [Drawing2] = [Drawing2]()
@State private var check: Bool = false
var body: some View {
GeometryReader { geo in
Path { path1 in
for drawings1 in self.drawings1 {
self.add1(drawings1: drawings1, toPath1: &path1)
}
self.add1(drawings1: self.currentDrawing1, toPath1: &path1)
}
.stroke(Color.orange, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
Path { path2 in
for drawings2 in self.drawings2 {
self.add2(drawings2: drawings2, toPath2: &path2)
}
self.add2(drawings2: self.currentDrawing2, toPath2: &path2)
}
.stroke(Color.black, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
Image("example1")
.resizable( resizingMode: .stretch)
.scaledToFit()
.clipped()
.position(x:200, y:200)
.frame(width: 100, height:100)
.gesture(
DragGesture()
.onChanged({ value1 in
let currentPoint1 = value1.location
self.currentDrawing1.points1.append(currentPoint1)
})
.onEnded({ value1 in
// the user has stopped drawing line 1
// generate a new Points array with interpolated points to fill any "gaps"
var iPts: [CGPoint] = []
iPts.append(self.currentDrawing1.points1[0])
for i in 1..<self.currentDrawing1.points1.count {
let p1 = self.currentDrawing1.points1[i-1]
let p2 = self.currentDrawing1.points1[i]
// default spacing is 8.0, but it can be adjusted if desired
let intPoints = pointsOnLine(start: p1, end: p2, spacing: 8.0)
iPts.append(contentsOf: intPoints[1...])
}
// update with interpolated points array
self.currentDrawing1.points1 = iPts
self.drawings1.append(self.currentDrawing1)
self.currentDrawing1 = Drawing1()
})
)
Image("example2")
.resizable( resizingMode: .stretch)
.scaledToFit()
.clipped()
.position(x:100, y:100)
.frame(width: 100, height:100)
.gesture(
DragGesture()
.onChanged({ value2 in
if check == false {
let currentPoint2 = value2.location
if self.currentDrawing2.points2.count == 0
{
// this is the first point triggered by the drag gesture
// so we don't need to compare it to anything
// just append current point to array
self.currentDrawing2.points2.append(currentPoint2)
return()
}
if drawings1.count > 0
{
// get the previous point from the current drag
// we can "!" force-unwrap it, because we know it exists
let prevPoint = self.currentDrawing2.points2.last!
// generate an array of interpolated points between last and current
// default spacing is 8.0, but it can be adjusted if desired
let iPoints = pointsOnLine(start: prevPoint, end: currentPoint2, spacing: 8.0)
// append the interpolated points to the array
self.currentDrawing2.points2.append(contentsOf: iPoints[1...])
// loop through those points, hit-testing for a rectangle surrounding each point in drawings1[0].points1
// we can skip the first point, because it was hit-tested on the previous .onChange() event
for newPoint in iPoints[1...]
{
for pt in drawings1[0].points1
{
if ((pt.x + 9 >= newPoint.x && pt.x - 9 <= newPoint.x)) && ((pt.y + 9 >= newPoint.y) && (pt.y - 9 <= newPoint.y))
{
self.currentDrawing2.points2.removeAll()
check = true
// we hit a "boxed point" so we don't need to check any other points
break
}
}
// if we've hit a "boxed point" we don't need to check the rest of the interpolated points
if check == true { break }
}
}
}
})
.onEnded( { value2 in
// if check is true, that means we intersected the line, so
// only execute this if we have NOT hit the line
if !check {
self.drawings2.append(self.currentDrawing2)
self.currentDrawing2 = Drawing2()
}
check = false
})
)
}
}
private func add1(drawings1: Drawing1, toPath1 path1: inout Path) {
let points1 = drawings1.points1
if points1.count > 1 {
path1.move(to: points1[0])
for pt in points1[1...] {
path1.addLine(to: pt)
}
}
}
private func add2(drawings2: Drawing2, toPath2 path2: inout Path) {
let points2 = drawings2.points2
if points2.count > 1 {
path2.move(to: points2[0])
for pt in points2[1...] {
path2.addLine(to: pt)
}
}
}
private func pointsOnLine(start: CGPoint, end: CGPoint, spacing: CGFloat = 8.0) -> [CGPoint] {
var points: [CGPoint] = []
// Calculate the differences in x and y
let deltaX = end.x - start.x
let deltaY = end.y - start.y
// Calculate the length of the line
let length = hypot(deltaX, deltaY)
// Normalize the direction of the line to unit length
let unitX = deltaX / length
let unitY = deltaY / length
// Add the first point (start point)
points.append(start)
// Add points at every spacing distance along the line
var currentDistance: CGFloat = spacing
while currentDistance < length {
let x = start.x + unitX * currentDistance
let y = start.y + unitY * currentDistance
points.append(CGPoint(x: x, y: y))
currentDistance += spacing
}
// Add the last point (end point)
points.append(end)
return points
}
}