As mentioned in the comments, it is difficult to follow the logic behind the OP's approach, as it doesn't offer additional advantages nor simplify things in any way that I can tell.
Since @Sweeper provided the solution for fixing the drag gesture (incorporated with some minor changes in the example below), here's how the same functionality could be achieved in a more SwiftUI way and more along the lines of what I believe @Sweeper was suggesting in earlier comments.
Some additional notes:
The star view (DrawStarView in your original code) shouldn't need an action closure to make everything work, given it already receives the point as a parameter. It makes everything unnecessarily convoluted.
The createStarPath
func in your code is basically the code of a star shape, so it should be turned into a shape to make it reusable instead of existing only inside the DrawStarView
view.
The overall complexity of various initializers and action closures is simply unnecessary - compare with the code below.
The GeometryReader
is not used, so it was replaced with a ZStack
to make layout and logic more intuitive.
Added a button that prints the (updated) star position to the console in order to show that there is no maintenance of final points position, since the the points get updated directly in the array, thanks to the binding passed to StarView
.
Added an optional double tap on star to reset its position, for convenience.
Note that the size of the stars can be controlled using the .frame
modifier, which wasn't possible previously. If needed, additional parameters could be added to StarView
for dynamically controlling the star size. UPDATE: I went ahead and added a size property to CustomPoint
that allows to set the star size now.
Here's the full code:
import SwiftUI
struct StarDragGesture: View {
//State values
@State private var points: [CustomPoint] = [CustomPoint(x: 100, y: 100), CustomPoint(x: 200, y: 200, size: CGSize(width: 50, height: 50)), CustomPoint(x: 300, y: 100)]
//Body
var body: some View {
ZStack {
//Background color
Color.blue
.ignoresSafeArea()
//Display a star for every point in the points array, at the specified position
ForEach($points) { $point in
StarView(position: $point)
}
//Button for checking the updated star positions in the console
Button {
print("-----")
for (index, point) in points.enumerated() {
print("Point \(index + 1) - X: \(point.x), Y: \(point.y)")
}
} label: {
Text("Check positions")
.foregroundStyle(.blue)
}
.buttonStyle(.borderedProminent)
.tint(.white)
}
}
}
struct StarView: View {
//Parameters
@Binding var position: CustomPoint
//State values
@State private var lastTranslation: CGSize = .zero
@State private var initialPosition: CustomPoint?
//Body
var body: some View {
StarShape(points: 5, innerRatio: 0.4)
.fill(Color.white)
.shadow(radius: 5)
.frame(width: position.size.width, height: position.size.height)
.position(position.coordinates)
//Reset star position on double tab
.onTapGesture(count: 2) {
if let initialPosition {
position = initialPosition
}
}
.gesture(dragGesture)
//Optional - Capture initial position for resetting
.onAppear {
initialPosition = position
}
}
private var dragGesture: some Gesture {
DragGesture(minimumDistance: 0)
.onChanged { value in
updatePosition(value.translation)
lastTranslation = value.translation
}
.onEnded { value in
updatePosition(value.translation)
lastTranslation = .zero
}
}
//Helper function for updating the position based on gesture translation
private func updatePosition(_ translation: CGSize ) {
position.x += translation.width - lastTranslation.width
position.y += translation.height - lastTranslation.height
}
}
struct StarShape: Shape {
var points: Int = 5
var innerRatio: CGFloat = 0.5 // Adjusts the depth of the star's inner points
func path(in rect: CGRect) -> Path {
guard points >= 2 else { return Path() }
let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
let angle = 2 * .pi / CGFloat(points)
let radius = min(rect.width, rect.height) / 2
var path = Path()
let startAngle = -CGFloat.pi / 2
for i in 0..<points * 2 {
let rotationAngle = startAngle + angle * CGFloat(i) / 2
let pointRadius = i % 2 == 0 ? radius : radius * innerRatio
let x = center.x + pointRadius * cos(rotationAngle)
let y = center.y + pointRadius * sin(rotationAngle)
if i == 0 {
path.move(to: CGPoint(x: x, y: y))
} else {
path.addLine(to: CGPoint(x: x, y: y))
}
}
path.closeSubpath()
return path
}
}
struct CustomPoint: Identifiable {
let id: UUID = UUID()
var x: CGFloat
var y: CGFloat
//Computed properties
var coordinates: CGPoint {
CGPoint(x: x, y: y)
}
//Optional property for possibly creating stars of various sizes
var size: CGSize = CGSize(width: 100, height: 100)
}
#Preview {
StarDragGesture()
}
Give this a try and let me know if it makes sense or if it still makes things difficult for your use case.