在 SwiftUI 中在两个圆之间画一条直线

问题描述 投票:0回答:2

我一直在尝试在 SwiftUI 中在 2 个圆(第一个和第二个圆)之间画一条线的不同方法。

我的视图基本上是 10 x 5 网格中 50 个圆圈的网格。

发现了一些关于使用首选项键的其他线程和另一个建议的 GeometryReader,但将其应用到我的视图似乎没有帮助。我决定实现一种自定义方法来查找我视图中每个圆的 CGPoint 坐标,它可以工作,但不是我想要的方式。

我的目的是找到网格中每个圆的中心的CGPoint,这样我就可以均匀地绘制到每个圆的中心的线,但是我只是没有得到正确的计算,有人能帮我指出这里出了什么问题吗?

这是我到目前为止所拥有的,

calculateCirclePosition()
是我计算每个坐标的方式。

enter image description here

这是我的查看代码,供以下参考:

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)
        }
}

ios swift swiftui mobile
2个回答
0
投票

您可以使用

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
实现将所有圆圈的偏好字典合并在一起,这样我们就可以在
onPreferenceChange
中获得整个字典。

在视图中,您可以执行以下操作:

let rows = 10
let columns = 5

// use a state to track the start and end points of the line
@State private var line = Line()

var body: some View {
    ZStack {
        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("ZStack"))
                    // 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)
        .onPreferenceChange(CirclePositionsKey.self) { value in
            if let p1 = value[0], let p2 = value[1] {
                // update the line's points in onPreferenceChange
                line.start = p1
                line.end = p2
            }
        }
        
        Path { path in
            path.move(to: line.start)
            path.addLine(to: line.end)
        }.stroke(Color.red, lineWidth: 2)
    }
    .coordinateSpace(.named("ZStack"))
}
struct Line {
    var start: CGPoint = .zero
    var end: CGPoint = .zero
}

也就是说,我会使用

Canvas
来绘制圆圈和线条。
Canvas
让您可以更好地控制在哪里绘制事物。


0
投票

正如 Sweeper 所评论的,绘制网格的更简单方法可能是使用

Canvas

但是,如果您确实想使用

LazyVGrid
,那么可以简单地使用圆圈上的叠加来绘制线条。这样,定位是自动的,完全省去了计算。

  • A
    GeometryReader
    可用于查找每个网格单元的大小。
  • 偏移量可用于与行或列中前面的圆重叠。
  • 需要调整网格间距。或者,您可以将间距设置为 0,并在圆圈周围使用填充。
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 {
                                        Rectangle()
                                            .fill(.orange)
                                            .frame(width: 2)
                                            .offset(y: -(proxy.size.height / 2) - (spacing / 2))
                                    }
                                    if !index.isMultiple(of: columns) {
                                        Rectangle()
                                            .fill(.red)
                                            .frame(height: 2)
                                            .offset(x: -(proxy.size.width / 2) - (spacing / 2))
                                    }
                                }
                                .frame(maxWidth: .infinity, maxHeight: .infinity)
                            }
                        }
                }
            }
            .padding(10)
        }
    }
}

Screenshot

© www.soinside.com 2019 - 2024. All rights reserved.