如何用SwiftUI绘制方向箭头?

How to draw a directional arrow head with SwiftUI?

我用swiftui实现了一个可以识别用户手势和画线的demo。 现在我希望它在线的末尾添加一个箭头,不同的线段有不同角度的箭头。我该怎么办?

这是我的代码

struct LineView: View {
    @State var removeAll = false
    @State var lines = [CGPoint]()
    var body: some View {
        ZStack {
            Rectangle()
                .cornerRadius(20)
                .opacity(0.1)
                .shadow(color: .gray, radius: 4, x: 0, y: 2)
            Path { path in
                path.addLines(lines)
            }
            .stroke(lineWidth: 3)
        }
        .gesture(
            DragGesture()
                .onChanged { state in
                    if removeAll {
                        lines.removeAll()
                        removeAll = false
                    }
                    
                    lines.append(state.location)
                }
                .onEnded { _ in
                    removeAll = true
                }
        )
        .frame(width: 370, height: 500)
    }
}

有很多方法可以确定如何定位和旋转行尾的箭头。大多数情况下,您需要最后两点来确定箭头的位置和旋转。

如果您已经绘制了箭头(路径或图像),那么使用变换可能是最简单的方法。它应该是这样的:

    private func arrowTransform(lastPoint: CGPoint, previousPoint: CGPoint) -> CGAffineTransform {
        let translation = CGAffineTransform(translationX: lastPoint.x, y: lastPoint.y)
        let angle = atan2(lastPoint.y-previousPoint.y, lastPoint.x-previousPoint.x)
        let rotation = CGAffineTransform(rotationAngle: angle)
        return rotation.concatenating(translation)
    }

这里的关键是使用 atan2,这是一个真正的救星。它基本上是反正切的,不需要额外的 if-statements 来确定两点之间的角度。它接受 delta-y 和 delta-x;仅此而已。 使用这些数据,您可以创建一个平移矩阵和一个旋转矩阵,然后将它们连接起来以获得最终结果。

要应用它,您只需像下面这样使用它:

arrowPath()
    .transform(arrowTransform(lastPoint: lines[lines.count-1], previousPoint: lines[lines.count-2]))
    .fill()

我这里只是简单的用了一个路径,在(0, 0)周围画了一个箭头,然后填充

我用的全部是:

struct LineView: View {
    @State var removeAll = false
    @State var lines = [CGPoint]()
    
    private func arrowPath() -> Path {
        // Doing it rightwards
        Path { path in
            path.move(to: .zero)
            path.addLine(to: .init(x: -10.0, y: 5.0))
            path.addLine(to: .init(x: -10.0, y: -5.0))
            path.closeSubpath()
        }
    }
    
    private func arrowTransform(lastPoint: CGPoint, previousPoint: CGPoint) -> CGAffineTransform {
        let translation = CGAffineTransform(translationX: lastPoint.x, y: lastPoint.y)
        let angle = atan2(lastPoint.y-previousPoint.y, lastPoint.x-previousPoint.x)
        let rotation = CGAffineTransform(rotationAngle: angle)
        return rotation.concatenating(translation)
    }
    
    var body: some View {
        ZStack {
            Rectangle()
                .cornerRadius(20)
                .opacity(0.1)
                .shadow(color: .gray, radius: 4, x: 0, y: 2)
            Path { path in
                path.addLines(lines)
            }
            .stroke(lineWidth: 3)
            if lines.count >= 2 {
                arrowPath()
                    .transform(arrowTransform(lastPoint: lines[lines.count-1], previousPoint: lines[lines.count-2]))
                    .fill()
            }
        }
        .gesture(
            DragGesture()
                .onChanged { state in
                    if removeAll {
                        lines.removeAll()
                        removeAll = false
                    }
                    
                    lines.append(state.location)
                }
                .onEnded { _ in
                    removeAll = true
                }
        )
        .frame(width: 370, height: 500)
    }
}

它仍然需要一些工作。删除线上的最后一个点或移动箭头以与最后一行重叠。但我认为你应该可以从这里拿走它。