iosswiftswiftuionchangedraggesture

DragGesture.onChange: for loop overloads CPU and freeze app


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.


Solution

  • 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:

    Non-Intersecting

    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:

    Intersecting

    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:

    LineWidth

    they do NOT intersect! That's because the actual line paths are:

    ActualLines


    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:

    enter image description here

    "under-the-hood" the paths get closed:

    enter image description here

    if we fill them:

    enter image description here

    .intersection gets this path (yellow):

    enter image description here

    and .lineIntersection returns the portion of the line from that intersection (yellow again):

    enter image description here

    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:

    enter image description here

    we get a closed path (fill red):

    enter image description here

    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:

    enter image description here

    None of those points is inside the red stroked-path.

    To "fix" that...

    Approach 1:

    Approach 2 (if Approach 1 induces lag, which it probably won't):

    Pretty 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...

    Animate Explanation

    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
        }
        
    }