SwiftUI - 在视图之间绘制(弯曲)路径

Big*_*air 3 line ios swift swiftui geometryreader

用非常基本的术语来说,在我的 Android 应用程序中,我有一个屏幕可以绘制圆圈,然后用曲线将它们连接起来。

我需要的草图

我正在尝试在 SwiftUI 中重新创建它。

我发现这个问题看起来与我正在寻找的问题非常相似,但不幸的是答案非常简短,即使在阅读了大约 10 个不同的博客和 5 个视频之后,我仍然没有完全理解它。
我可以在 SwiftUI 中布局后获取“View”的位置吗?


所以基本逻辑是我以某种方式GeometryReader用来获取我创建的每个元素的.midX.midY坐标Circle,然后Paths在它们之间进行绘制。我唯一的问题是在创建圆之后获取这些坐标。

如何将路径添加到屏幕上,在ZStack前面Circles,路径作为一个自定义形状在后面?


更多信息:
在 Android 上,最终结果如下所示:

在此输入图像描述

基本上我有一个Challenge具有名称和一些详细文本的对象,我将它们像这样布局,以便它在视觉上代表用户的“旅程”。

所以我真正需要的是知道如何布置一些圆圈/图像(上面有文字),然后绘制连接它们的线。每个这样的挑战圈都需要可点击才能打开详细视图。

Phi*_*hov 5

GeometryReader为您提供容器视图的信息,您可以获取尺寸geometry.size,然后计算中点等

内部GeometryReader布局是ZStack,所以所有项目都会一个一个地叠在一起

绘制曲线的简单方法是Path { path in },在这个块内,您可以向路径添加直线/曲线,而不是stoke()

您可以通过两种方式绘制圆形:第一种是再次使用Path,添加圆角矩形和fill()它。

另一种选择是放置Circle()并添加偏移量

我用第一种方式用蓝色来做,第二种用绿色做,半径较小。我随机选择了曲线控制点只是为了给你一个想法

let circleRelativeCenters = [
    CGPoint(x: 0.8, y: 0.2),
    CGPoint(x: 0.2, y: 0.5),
    CGPoint(x: 0.8, y: 0.8),
]

var body: some View {
    GeometryReader { geometry in
        let normalizedCenters = circleRelativeCenters
            .map { center in
                CGPoint(
                    x: center.x * geometry.size.width,
                    y: center.y * geometry.size.height
                )
            }
        Path { path in
            var prevPoint = CGPoint(x: normalizedCenters[0].x / 4, y: normalizedCenters[0].y / 2)
            path.move(to: prevPoint)
            normalizedCenters.forEach { center in
                    path.addQuadCurve(
                        to: center,
                        control: .init(
                            x: (center.x + prevPoint.x) / 2,
                            y: (center.y - prevPoint.y) / 2)
                    )
                    prevPoint = center
            }
        }.stroke(lineWidth: 3).foregroundColor(.blue).background(Color.yellow)
        Path { path in
            let circleDiamter = geometry.size.width / 5
            let circleFrameSize = CGSize(width: circleDiamter, height: circleDiamter)
            let circleCornerSize = CGSize(width: circleDiamter / 2, height: circleDiamter / 2)
            normalizedCenters.forEach { center in
                path.addRoundedRect(
                    in: CGRect(
                        origin: CGPoint(
                            x: center.x - circleFrameSize.width / 2,
                            y: center.y - circleFrameSize.width / 2
                        ), size: circleFrameSize
                    ),
                    cornerSize: circleCornerSize
                )
            }
        }.fill()
        ForEach(normalizedCenters.indices, id: \.self) { i in
            let center = normalizedCenters[i]
            let circleDiamter = geometry.size.width / 6
            let circleFrameSize = CGSize(width: circleDiamter, height: circleDiamter)
            Circle()
                .frame(size: circleFrameSize)
                .offset(
                    x: center.x - circleFrameSize.width / 2,
                    y: center.y - circleFrameSize.width / 2
                )
        }.foregroundColor(.green)
    }.frame(maxWidth: .infinity, maxHeight: .infinity).foregroundColor(.blue).background(Color.yellow)
}
Run Code Online (Sandbox Code Playgroud)

结果:


在里面Path { path in我可以使用forEach,因为它不再是视图生成器的范围。

如果您需要对修饰符进行一些计算,您可以使用下一个技巧:

func circles(geometry: GeometryProxy) -> some View {
    var points = [CGPoint]()
    var prevPoint: CGPoint?
    (0...5).forEach { i in
        let point: CGPoint
        if let prevPoint = prevPoint {
            point = CGPoint(x: prevPoint.x + 1, y: prevPoint.y)
        } else {
            point = .zero
            
        }
        points.append(point)
        prevPoint = point
    }
    return ForEach(points.indices, id: \.self) { i in
        let point = points[i]
        Circle()
            .offset(
                x: point.x,
                y: point.y
            )
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以在体内使用它,就像circles(geometry: geometry).foregroundColor(.green)