Home > Back-end >  SwiftUI: How to connect more than 2 dots, one by one
SwiftUI: How to connect more than 2 dots, one by one

Time:04-30

I am making an app where I am generating points with random position. I want to connect first point with the second one with the line, and that the second with the third, etc. My issue is that it is connecting second with the third but in the same time the first is being connected with the third one. And I don't want that. Please help.

This is my code:

struct Dot: Hashable {
    var x: CGFloat
    var y: CGFloat
}

struct TestShit: View {
    @State var lastx = 0.0
    @State var lasty = 0.0
    @State var dots = [Dot]()
    var body: some View {
        VStack{
            ZStack{
                ForEach(dots, id: \.self){ dot in
                    Circle()
                        .frame(width: 5, height: 5)
                        .position(x: dot.x, y: dot.y)
                    Path { path in
                        path.move(to: CGPoint(x: lastx, y: lasty))
                        path.addLine(to: CGPoint(x: dot.x, y: dot.y))
                    }.stroke(.green, lineWidth: 5)
                }
            }
            Spacer()
            Button {
                let randx = CGFloat.random(in: 100...300)
                let randy = CGFloat.random(in: 100...300)
                dots.append(Dot(x: randx, y: randy))
                lastx = randx
                lasty = randy
            } label: {
                Text("Button")
            }
        }
    }
}

CodePudding user response:

Here I only maintain 1 path, and append to it each time rather than more than 1 path in the ForEach

import Combine
import SwiftUI

struct Dot: Hashable {
    var x: CGFloat
    var y: CGFloat
}

class Model: ObservableObject {
    @Published
    var dots: [Dot] = []

    @Published
    var path: UIBezierPath?

    func addDot(x: CGFloat, y: CGFloat) {
        dots.append(Dot(x: x, y: y))
        let randomPoint = CGPoint(x: x, y: y)

        guard let path = path else {
            path = UIBezierPath()
            path?.move(to: randomPoint)
            path?.lineWidth = 2
            return
        }

        path.addLine(to: CGPoint(x: x, y: y))
    }
}

struct TestIt: View {
    @ObservedObject
    var model: Model
    
    var body: some View {
        VStack{
            ZStack{
                if let path = model.path {
                    Path(path.cgPath)
                        .stroke(.green, lineWidth: 5)
                }
                ForEach(model.dots, id: \.self){ dot in
                    Circle()
                        .frame(width: 5, height: 5)
                        .position(x: dot.x, y: dot.y)
                }
            }
            Spacer()
            Button {
                let randx = CGFloat.random(in: 100...300)
                let randy = CGFloat.random(in: 100...300)

                model.addDot(x: randx, y: randy)
            } label: {
                Text("Button")
            }
        }
    }
}

You might want something a little nicer looking like

Path(path.cgPath.copy(strokingWithWidth: 5, lineCap: .round, lineJoin: .round, miterLimit: 0))
                .stroke(.green, lineWidth: 5)

CodePudding user response:

The problem is that with this loop:

Path { path in
    path.move(to: CGPoint(x: lastx, y: lasty))
    path.addLine(to: CGPoint(x: dot.x, y: dot.y))
}.stroke(.green, lineWidth: 5)

you are creating multiple line segments by moving to the same point, and then adding a line to a different point.

What you want to do is:

  • set a "start" point
  • move to that point
  • for each dot, add a line to the next dot

Take a look at the difference here:

import SwiftUI

struct Dot: Hashable {
    var x: CGFloat
    var y: CGFloat
}

struct DotsView: View {
    // initialize start point
    var startX = CGFloat.random(in: 100...300)
    var startY = CGFloat.random(in: 100...300)
    
    @State var dots = [Dot]()
    var body: some View {
        VStack{
            ZStack{
                Path { path in
                    // move to start point
                    path.move(to: CGPoint(x: startX, y: startY))
                    dots.forEach { dot in
                        // add line to each dot
                        path.addLine(to: CGPoint(x: dot.x, y: dot.y))
                    }
                }.stroke(.green, lineWidth: 5)
                // draw the start point circle in red
                Circle()
                    .fill(Color.red)
                    .frame(width: 5, height: 5)
                    .position(x: startX, y: startY)
                // draw each dot circle in blue
                ForEach(dots, id: \.self){ dot in
                    Circle()
                        .fill(Color.blue)
                        .frame(width: 5, height: 5)
                        .position(x: dot.x, y: dot.y)
                }
            }
            Spacer()
            Button {
                let randx = CGFloat.random(in: 100...300)
                let randy = CGFloat.random(in: 100...300)
                // add a dot point
                dots.append(Dot(x: randx, y: randy))
            } label: {
                Text("Button")
            }
        }
    }
}
  • Related