I have an array of defined points, and I'm using these points to draw a star at each location. The star view should simply report back the drag gesture for updating each point's position. Currently, the code works partially: the drag amount is not equal to the displacement of the stars.
My goal is to keep the star view as minimal as possible; I only want to use this view to read the drag amount. I would like the points' positions to be updated within a ForEach loop. How can I correctly convert the DragGesture value to a CGPoint in the ForEach to achieve this?
import SwiftUI
struct ContentView: View {
@State private var points: [CustomPoint] = [CustomPoint(x: 100, y: 100), CustomPoint(x: 200, y: 200), CustomPoint(x: 300, y: 100)]
var body: some View {
GeometryReader { proxy in
Color.blue
ForEach($points) { $point in
DrawStarView(customPoint: point) { value in
point.cgPointValue = CGPoint(x: point.cgPointValue.x + value.width, y: point.cgPointValue.y + value.height)
}
}
}
.padding()
}
}
struct CustomPoint: Identifiable {
init(x: CGFloat, y: CGFloat) {
self.cgPointValue = CGPoint(x: x, y: y)
}
let id: UUID = UUID()
var cgPointValue: CGPoint
}
struct DrawStarView: View {
init(customPoint: CustomPoint, action: @escaping (CGSize) -> Void) {
self.customPoint = customPoint
self.action = action
}
let customPoint: CustomPoint
let action: (CGSize) -> Void
var body: some View {
return createStarPath(at: customPoint.cgPointValue)
.fill(Color.white)
.shadow(radius: 5)
.gesture(dragGesture)
}
func createStarPath(at point: CGPoint, size: CGSize = CGSize(width: 100, height: 100)) -> Path {
let outerRadius = size.width / 2
let innerRadius = outerRadius / 2.5
let center = point
let pointsOnStar = 5
let angleIncrement = .pi * 2 / CGFloat(pointsOnStar * 2)
var path = Path()
for i in 0..<pointsOnStar * 2 {
let angle = angleIncrement * CGFloat(i) - .pi / 2
let radius = (i % 2 == 0) ? outerRadius : innerRadius
let x = center.x + radius * cos(angle)
let y = center.y + radius * sin(angle)
if i == 0 {
path.move(to: CGPoint(x: x, y: y))
} else {
path.addLine(to: CGPoint(x: x, y: y))
}
}
path.closeSubpath()
return path
}
private var dragGesture: some Gesture {
DragGesture(minimumDistance: 0)
.onChanged { value in
action(value.translation)
}
.onEnded { value in
action(value.translation)
}
}
}
translation
is the total translation since the gesture has started. It is not the difference between the location between this call to onChange
and the next call to onChange
.
If the difference is what you want, you should track the previous translation
using a separate @State
, and subtract that from the current translation
, and pass that into action
.
@State var lastTranslation: CGSize = .zero
private var dragGesture: some Gesture {
DragGesture(minimumDistance: 0)
.onChanged { value in
action(.init(
width: value.translation.width - lastTranslation.width,
height: value.translation.height - lastTranslation.height)
)
lastTranslation = value.translation
}
.onEnded { value in
action(.init(
width: value.translation.width - lastTranslation.width,
height: value.translation.height - lastTranslation.height)
)
lastTranslation = .zero
}
}