swiftuiuigesturerecognizercgpointcgpath

How to make a Line follow a CGPoint in SwiftUI?


I've created two CGPoints and a connecting line between them. My aim is the following: When I drag the second CGPoint with a SwiftUI DragGesture() the line will follow it (as if it's connected to it). At the moment the line doesn't follow the point. How can I do that?

enter image description here

Here's my code:

import SwiftUI

struct Point : View {
    var position: CGPoint

    init(_ position: CGPoint) {
        self.position = position
    }
    var body: some View {
        Circle()
            .frame(width: 20)
            .foregroundColor(.white)
            .position(x: position.x, y: position.y)
    }
}

LineWithPoints.swift

struct LineWithPoints : View {
    @State var point1 = Point(.init(x: 0,   y: 0))
    @State var point2 = Point(.init(x: 450, y: 0))
    @State private var offset1 = CGSize.zero
    
    var body: some View {
        let lineBetween = LineBetween(pointPosition: $point2.position)
        
        ZStack {
            lineBetween.stroke(.red, lineWidth: 2)
            point1;               
            point2
                .offset(x: offset1.width, y: offset1.height)
                .gesture(
                    DragGesture()
                        .onChanged { value in
                            let x = value.startLocation.x
                            let y = value.startLocation.y
                            let w = value.translation.width
                            let h = value.translation.height
                            offset1 = .init(width: (x + w) - point2.position.x,
                                           height: (y + h) - point2.position.y)
                        }
                )
        }
    }
    struct LineBetween : Shape, @unchecked Sendable {
        @Binding var pointPosition: CGPoint

        func path(in rect: CGRect) -> Path {
            var path = Path()
            path.move(to: CGPoint.zero)
            path.addLine(to: pointPosition)
            path.closeSubpath()
            return path
        }
    }
}

ContentView.swift

struct ContentView : View {
    var body: some View {
        ZStack {
            Color.black.ignoresSafeArea()
            LineWithPoints()
        }
    }
}

Solution

  • Here are some things which could do with correction:

    Here is a revised version which addresses the points above. It draws the line to a target point. This is a computed property that combines the position of point 2 with the drag offset.

    struct LineWithPoints : View {
        @State private var point1: Point
        @State private var point2: Point
        @State private var offset = CGSize()
    
        init(
            point1: CGPoint = CGPoint(x: 50, y: 200),
            point2: CGPoint = CGPoint(x: 325, y: 200)
        ) {
            self._point1 = State(initialValue: Point(point1))
            self._point2 = State(initialValue: Point(point2))
        }
    
        struct LineBetween : Shape {
            let point1: CGPoint
            let point2: CGPoint
    
            func path(in rect: CGRect) -> Path {
                var path = Path()
                path.move(to: point1)
                path.addLine(to: point2)
                path.closeSubpath()
                return path
            }
        }
    
        private var targetPoint: CGPoint {
            CGPoint(
                x: point2.position.x + offset.width,
                y: point2.position.y + offset.height
            )
        }
    
        var body: some View {
            ZStack {
                LineBetween(
                    point1: point1.position,
                    point2: targetPoint
                )
                .stroke(.red, lineWidth: 2)
                point1
                point2
                    .offset(x: offset.width, y: offset.height)
                    .gesture(
                        DragGesture()
                            .onChanged { value in
                                offset = value.translation
                            }
                            .onEnded { _ in
                                point2 = Point(targetPoint)
                                offset = CGSize()
                            }
                    )
            }
        }
    }
    

    LineBetween