Я пробовал разные способы рисования линии между двумя кругами (первым и вторым) в SwiftUI.
Мой взгляд — это, по сути, сетка из 50 кругов в сетке 10 x 5.
Я нашел несколько других тем об использовании ключей предпочтений и еще одного предложенного GeometryReader, но применение его к моему представлению, похоже, не помогает. Я решил реализовать собственный способ поиска координат CGPoint для каждого круга в моем представлении, и он работает, но не так, как я хочу.
Мое намерение состоит в том, чтобы найти CGPoint центра каждого круга в сетке, чтобы я мог равномерно рисовать линии к каждому центру круга, однако я просто не понимаю правильных вычислений, может ли кто-нибудь помочь мне указать, что здесь не так?
Это то, что у меня есть на данный момент, и calculateCirclePosition()
так я вычисляю каждую координату.
Это мой код просмотра ниже для справки ниже:
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)
}
}
Вы можете использовать GeometryReader
для чтения рамок кругов в координатном пространстве ZStack
(которое является координатным пространством, в котором нарисован Path
).
Вместо использования onAppear
для обновления словаря для отслеживания центров кругов поместите словарь в PreferenceKey
:
struct CirclePositionsKey: PreferenceKey {
static var defaultValue: [Int: CGPoint] { [:] }
static func reduce(value: inout [Int : CGPoint], nextValue: () -> [Int : CGPoint]) {
value.merge(nextValue(), uniquingKeysWith: { $1 })
}
}
Предпочтением каждого круга будет одна пара ключ-значение. Реализация reduce
объединяет словари предпочтений всех кругов вместе, так что мы получаем весь словарь в overlayPreferenceValue, где мы можем нарисовать путь в виде наложения.
В представлении вы можете сделать:
let rows = 10
let columns = 5
var body: some View {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columns)) {
ForEach(0..<rows * columns, id: \.self) { index in
GeometryReader { geo in
let frame = geo.frame(in: .named("DrawingSpace"))
// read the circle centre here
let center = CGPoint(x: frame.midX, y: frame.midY)
Circle()
.foregroundColor(.blue)
.preference(key: CirclePositionsKey.self, value: [
index: center
])
}
.frame(width: 50, height: 50)
}
}
.padding(10)
.overlayPreferenceValue(CirclePositionsKey.self) { value in
// draw the path as an overlay, based on the preference value
if let p1 = value[0], let p2 = value[1] {
Path { path in
path.move(to: p1)
path.addLine(to: p2)
}.stroke(Color.red, lineWidth: 2)
}
}
.coordinateSpace(.named("DrawingSpace"))
}
Тем не менее, вместо этого я бы использовал Canvas
, чтобы рисовать круги и линии. Canvas
дает вам гораздо больше контроля над тем, где именно рисовать.
Например:
let rows = 10
let columns = 5
var body: some View {
Canvas { gc, size in
let radius: CGFloat = 25
let spacing: CGFloat = 10
// calculate size of the things to draw...
let totalWidth = CGFloat(columns) * (radius * 2 + spacing) - spacing
let totalHeight = CGFloat(rows) * (radius * 2 + spacing) - spacing
// so that we can calculate some offsets in order to center-align the whole drawing
let xOffset = (size.width - totalWidth) / 2
let yOffset = (size.height - totalHeight) / 2
gc.translateBy(x: xOffset, y: yOffset)
for i in 0..<rows {
for j in 0..<columns {
let frame = CGRect(
x: CGFloat(j) * (radius * 2 + spacing),
y: CGFloat(i) * (radius * 2 + spacing),
width: radius * 2,
height: radius * 2
)
gc.fill(Path(ellipseIn: frame), with: .color(.blue))
}
}
func centerOfCircle(atColumn col: Int, row: Int) -> CGPoint {
let x = CGFloat(col) * (radius * 2 + spacing) + radius
let y = CGFloat(row) * (radius * 2 + spacing) + radius
return CGPoint(x: x, y: y)
}
let line = Path {
$0.move(to: centerOfCircle(atColumn: 0, row: 0))
$0.addLine(to: centerOfCircle(atColumn: 1, row: 0))
}
gc.stroke(line, with: .color(.red), lineWidth: 2)
}
}
Canvas выглядит как отличный вариант для изучения. Я проверю это. Спасибо за ваши решения по обоим предложениям, они выглядят хорошо и работают для меня. Я думаю, что буду больше изучать пользователя Canvas.
Как прокомментировал Sweeper, более простой способ нарисовать сетку — использовать Canvas
.
Однако, если вы действительно хотите использовать LazyVGrid
, то линии можно просто нарисовать, наложив их на круги. Таким образом, позиционирование происходит автоматически и вычисления полностью исключаются.
GeometryReader
можно использовать, чтобы найти размер каждой ячейки сетки.struct CircleGridView: View {
let rows = 10
let columns = 5
let spacing: CGFloat = 10
var body: some View {
ZStack {
LazyVGrid(
columns: Array(
repeating: GridItem(.flexible(), spacing: spacing),
count: columns
),
spacing: spacing
) {
ForEach(0..<rows * columns, id: \.self) { index in
Circle()
.foregroundColor(.blue)
.frame(width: 50, height: 50)
.frame(maxWidth: .infinity)
.overlay {
GeometryReader { proxy in
ZStack {
if (index / columns) > 0 {
Color.orange
.frame(width: 2)
.offset(y: -(proxy.size.height + spacing) / 2)
}
if !index.isMultiple(of: columns) {
Color.red
.frame(height: 2)
.offset(x: -(proxy.size.width + spacing) / 2)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
}
.padding(10)
}
}
}
Спасибо за ваше решение. И да, Canvas выглядит как отличный вариант для изучения, а не для выполнения подобных вычислений.
Гораздо проще это сделать, нарисовав
Canvas
. Есть ли проблемы с использованиемCanvas
для рисования кругов и линий?