如何用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)
}
}
它仍然需要一些工作。删除线上的最后一个点或移动箭头以与最后一行重叠。但我认为你应该可以从这里拿走它。
我用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)
}
}
它仍然需要一些工作。删除线上的最后一个点或移动箭头以与最后一行重叠。但我认为你应该可以从这里拿走它。