I’ve been trying different ways of drawing a line between 2 circles(first and second circle) in SwiftUI.
My view is basically a grid of 50 circles in a 10 x 5 grid.
Have found some other threads on using preference keys and another suggested GeometryReader but applying it to my view does not seem to help. I decided to implement a custom way to find the CGPoint coordinates for each circle in my view and it works but not the way I want it to.
My intention is to find the CGPoint of the center of each circle in the grid so I can evenly draw lines to each center of the circle however I’m just not getting the calculation right, could anyone help me point out what’s wrong here?
This is what I have so far and calculateCirclePosition()
is how I’m calculating each coordinate.
This is my view code below for for reference below:
import SwiftUI
struct CircleGridView: View {
let rows = 10
let columns = 5
@State private var coordinates: [Int :CGPoint] = [:]
var body: some View {
ZStack {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columns)) {
ForEach(0..<rows * columns, id: .self) { index in
Circle()
.foregroundColor(.blue)
.frame(width: 50, height: 50)
.onAppear {
DispatchQueue.main.async {
let circlePosition = calculateCirclePosition(for: index)
coordinates[index] = circlePosition
print("Index (index): (circlePosition)")
}
}
}
}
.padding(10)
if let startPoint = coordinates[0], let endPoint = coordinates[1] {
Path { path in
path.move(to: startPoint)
path.addLine(to: endPoint)
}.stroke(Color.red, lineWidth: 2)
}
}
}
func calculateCirclePosition(for index: Int) -> CGPoint {
let row = index / columns
let column = index % columns
let circleSize: CGFloat = 50
let circleSpacing: CGFloat = 10
let yOffset: CGFloat = 85
let xOffset: CGFloat = 15
let x = CGFloat(column) * (circleSize + circleSpacing) + circleSize / 2 + circleSpacing + xOffset
let y = CGFloat(row) * (circleSize + circleSpacing) + circleSize / 2 + circleSpacing + yOffset
return CGPoint(x: x, y: y)
}
}
2
Answers
You can use a
GeometryReader
to read the circles’ frames, in the coordinate space of theZStack
(which is the coordinate space in which thePath
is drawn).Instead of using
onAppear
to update the dictionary to track the circles’ centres, put the dictionary in aPreferenceKey
:Each circle’s preference will be a single key-value pair. The
reduce
implementation merges all the circles’ preference dictionaries together, so that we get the whole dictionary inonPreferenceChange
.n the view, you can do:
That said, I would use a
Canvas
to draw the circles and lines instead.Canvas
gives you much more control over where exactly to draw things.For example:
As Sweeper has commented, an easier way to draw the grid might be to use a
Canvas
.However, if you really want to use a
LazyVGrid
, then the lines can simply be drawn using overlays over the circles. This way, the positioning is automatic and the computation is completely eliminated.GeometryReader
can be used to find the size of each grid cell.