I am creating an application where the user can configure custom spots (circles) for spot booking in events via drag and drop.
Each spot is a Circle
filled with a gesture that detects dragging and updates the position if it does not overlap with an existing spot by passing the callbacks from the parent view (ContentView
) to the child view (Spot
). This works, however, I am only able to drag the spots one by one position before it stops, although my cursor is still dragging.
Current behaviour: https://imgur.com/a/hlXOcuZ
Expected behaviour: https://imgur.com/a/1UI57wj
This is my simplified ContentView.swift
struct ContentView: View {
@State var spots: [CGPoint] = [CGPoint(x: 100, y: 100), CGPoint(x: 200, y: 200)]
var body: some View {
ZStack {
ForEach(spots, id: .x) {point in
Spot(location: point,
checkOverlap: {point in
let overlappingSpots = spots.filter{spot in
let distance = calculateDistance(centerOne: point, centerTwo: spot)
return overlaps(distance: distance, radius: 25)
}
return overlappingSpots.count > 0
},
updatePosition: {newPosition, oldPosition in
self.spots = spots.map{spot in
if (spot != oldPosition) {
return spot
}
return newPosition
}
})
}
}
}
private func calculateDistance(centerOne: CGPoint, centerTwo: CGPoint) -> CGFloat {
let xSquared = pow(centerOne.x - centerTwo.x, 2)
let ySquared = pow(centerOne.y - centerTwo.y, 2)
return sqrt(xSquared + ySquared)
}
private func overlaps(distance: CGFloat, radius: CGFloat) -> Bool {
return distance > 0 && distance < (radius + radius)
}
}
and my Spot.swift
struct Spot: View {
@GestureState private var startLocation: CGPoint?
@State var currentLocation: CGPoint
var checkOverlap: (CGPoint) -> Bool
var updatePosition: (CGPoint, CGPoint) -> Void
init(location: CGPoint,
checkOverlap: @escaping (CGPoint) -> Bool,
updatePosition: @escaping (CGPoint, CGPoint) -> Void) {
self.checkOverlap = checkOverlap
self.updatePosition = updatePosition
_currentLocation = State(initialValue: location)
}
var body: some View {
let dragGesture = DragGesture()
.onChanged { value in
var newLocation = startLocation ?? currentLocation
newLocation.x += value.translation.width
newLocation.y += value.translation.height
let overlaps = checkOverlap(newLocation)
if (overlaps) {
return
}
let existingLocation = self.currentLocation
self.currentLocation = newLocation
updatePosition(newLocation, existingLocation)
}.updating($startLocation) { value, state, transaction in
state = state ?? currentLocation
}
return Circle()
.fill(Color.red)
.frame(width: 50, height: 50)
.position(currentLocation)
.gesture(dragGesture)
}
}
question from:
https://stackoverflow.com/questions/65874227/swiftui-drag-and-drop-prevent-overlapping-child-views 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…