Search code examples
swiftswiftuidrawinggeometryreader

Drawing 2 dots and a line between them in SwiftUI


Swift 5.5 iOS 15

Drawing 2 dots at random places, noting their origins and then trying to use the information to draw a line between them.

Almost there, and yet the result is wrong? What am I doing wrong?

struct ContentView: View {
@State var cords:[CGPoint] = []
@State var showMe = false
var body: some View {
    ZStack {
        ForEach((0..<2), id:\.self) {foo in
            Circle()
                .fill(Color.blue)
                .frame(width: 64, height: 64)
            
                .background(GeometryReader { geo in
                    Circle()
                        .stroke(Color.red, lineWidth: 2)
                        .frame(width: 64, height: 64)
                        .onAppear {
                            cords.append(CGPoint(x: geo.frame(in: .global).origin.x, y: geo.frame(in: .global).origin.y))
                        }
                })
                .position(returnRandom())
                .task {
                    if cords.count > 1 {
                        print("cords \(cords)")
                        showMe = true
                    }
                }
        }
    }
    ZStack {
        if showMe {
            Connected(cords: $cords)
                .stroke(Color.black, lineWidth: 1)
        }
    }
}
func returnRandom() -> CGPoint {
    let x = Double.random(in: 0..<width)
    let y = Double.random(in: 0..<height)
    return CGPoint(x: x, y: y)
}
}

struct Connected: Shape {
@Binding var cords:[CGPoint]
func path(in rect: CGRect) -> Path {
    var path = Path()
    path.move(to: cords[0])
    path.addLine(to: cords[1])
    return path
}
}

Also tried ...

cords.append(CGPoint(x: geo.frame(in: .global).midX, y: geo.frame(in: .global).midY))

Solution

  • The issue is the way you implemented this.

    You are modifying your state after Circles have appeared and the coordinates have been calculated.

    .task {
        if cords.count > 1 {
            print("cords \(cords)")
            showMe = true
        }
    }    
    

    so your view gets reavaluated and your Circles are redrawn at a new position without calling onAppear(). Your line is still drawn with the old set of coordinates.

    Set a breakpoint at:

    let x = Double.random(in: 0..<width)
    

    and you will see it gets hit 4 times.

    Better:

    Create an empty array in view for coordinates. Wrap ForEach in condition. Set an onApear modifier on the view containing your circles. Calculate your coordinates and append them. Draw your lines.

    Or:

    Create an initializer to calculate Points. Or initialize the Array with random Coordinates.