SwiftUI - 在视图之间绘制(弯曲)路径 [英] SwiftUI - Drawing (curved) paths between views
问题描述
用非常基本的术语来说,在我的 Android 应用中,我有一个屏幕,可以绘制圆圈,然后用曲线将它们连接起来.
我正在尝试在 SwiftUI 中重新创建它.
我发现这个问题与我正在寻找的问题非常相似,但不幸的是答案非常简短,即使阅读了大约 10 个不同的博客和 5 个视频,我仍然没有完全理解它.
基本上我有一个 Challenge
对象,它有一个名称和一些细节文本,我像这样布置它们,所以它在视觉上代表了一个旅程".为用户.
所以我真正需要的是知道如何布置一些圆圈/图像(上面有文字),然后绘制连接它们的线.每个这样的挑战圈都需要可点击以打开详细信息视图.
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主体:一些视图{GeometryReader { 几何输入让 normalizedCenters = circleRelativeCenters.map { 居中CG点(x: center.x * geometry.size.width,y: center.y * geometry.size.height)}路径{路径输入var prevPoint = CGPoint(x: normalizedCenters[0].x/4, y: normalizedCenters[0].y/2)path.move(to: prevPoint)normalizedCenters.forEach { 居中path.addQuadCurve(到:中心,控制:.init(x: (center.x + prevPoint.x)/2,y: (center.y - prevPoint.y)/2))prevPoint = 中心}}.stroke(lineWidth: 3).foregroundColor(.blue).background(Color.yellow)路径{路径输入让 circleDiamter = geometry.size.width/5让 circleFrameSize = CGSize(width: circleDiamter, height: circleDiamter)让 circleCornerSize = CGSize(width: circleDiamter/2, height: circleDiamter/2)normalizedCenters.forEach { 居中path.addRoundedRect(在:CGRect(原点:CGPoint(x: center.x - circleFrameSize.width/2,y: center.y - circleFrameSize.width/2),大小:circleFrameSize),角尺寸:圆角尺寸)}}.填()ForEach(normalizedCenters.indices, id: \.self) { i in让 center = normalizedCenters[i]让 circleDiamter = geometry.size.width/6让 circleFrameSize = CGSize(width: circleDiamter, height: circleDiamter)圆圈().frame(大小:circleFrameSize).抵消(x: center.x - circleFrameSize.width/2,y: center.y - circleFrameSize.width/2)}.foregroundColor(.green)}.frame(maxWidth: .infinity, maxHeight: .infinity).foregroundColor(.blue).background(Color.yellow)}
结果:
Inside Path { path in
我可以使用 forEach
,因为它不再是视图构建器的范围.
如果您需要对修改器进行一些计算,您可以使用下一个技巧:
func circles(geometry: GeometryProxy) ->一些视图{var 点 = [CGPoint]()var prevPoint: CGPoint?(0...5).forEach { i 在让点:CGPoint如果让 prevPoint = prevPoint {点 = CGPoint(x: prevPoint.x + 1, y: prevPoint.y)} 别的 {点 = .0}点附加(点)prevPoint = 点}返回 ForEach(points.indices, id: \.self) { i in让点 = 点 [i]圆圈().抵消(x: 点.x,y:点.y)}}
然后你可以像 circles(geometry: geometry).foregroundColor(.green)
In very basic terms, in my Android app, I have a screen that draws circles and then connects them with curved lines.
I am trying to recreate this in SwiftUI.
I found this question that seems very similar to what I am looking for, but unfortunately the answer is extremely short and even after reading about 10 different blog and 5 videos, I still did not fully understand it.
can I get the position of a `View` after layout in SwiftUI?
So the basic logic is I somehow use GeometryReader
to get the .midX
and .midY
coordinates of each Circle
I create and then draw Paths
between them. My only problem is getting ahold of these coordinates after creating the Circle.
And how do I add the paths to the screen, in a ZStack
with the Circles
in front and the paths as one custom shape in the back?
More info:
On Android the final result looks like this:
Basically I have a Challenge
object that has a name and some detail text and I lay them out like this so it visually represents a "journey" for the user.
So all I really need is to know how to lay out some circles/images (with text on them) and then draw lines connecting them. And each such challenge circle needs to be clickable to open a detail view.
GeometryReader
gives you information if container view, you can get size like geometry.size
and then calculate middle point, etc
Inside GeometryReader
layout is ZStack
, so all items gonna be one on top of each other
Easies way to draw curves is Path { path in }
, inside this block you can add lines/curves to the path, than you can stoke()
it
You can draw circles in two ways: first is again using Path
, adding rounded rects and fill()
it.
An other option is placing Circle()
and adding an offset
I did it in the first way in blue and in the second one in green with smaller radius. I selected curve control points randomly just to give you an idea
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)
}
Result:
Inside Path { path in
I can use forEach
, because it's not a scope of view builder anymore.
If you need to make some calculations for your modifiers, you can use next trick:
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
)
}
}
Then you can use it inside body like circles(geometry: geometry).foregroundColor(.green)
这篇关于SwiftUI - 在视图之间绘制(弯曲)路径的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!