SwiftUI - 在视图之间绘制(弯曲)路径 [英] SwiftUI - Drawing (curved) paths between views

查看:44
本文介绍了SwiftUI - 在视图之间绘制(弯曲)路径的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

用非常基本的术语来说,在我的 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)

一样在 body 内部使用它

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆