It has been a bit tough to achieve, and I don't know if it is even worth using. Maybe with some changes it can be scalable, but now it only works with Strings
. I built an extension
and a modifier
to make things draggable creating an overlay
to simulate the effect of the default draggable
modifier:
struct DragModifier: ViewModifier {
var tag: String
@Binding var dragLocation: CGPoint
/// To make the text move
@Binding var dragTranslation: CGSize
/// To keep track of the current cursor location
@Binding var dragInfo: String
func body(content: Content) -> some View {
content
.background { dragDetector(for: tag) }
}
private func dragDetector(for name: String) -> some View {
GeometryReader { proxy in
let frame = proxy.frame(in: .global)
let isDragLocationInsideFrame = frame.contains(dragLocation)
let isDragLocationInsideArea = isDragLocationInsideFrame &&
Circle().path(in: frame).contains(dragLocation)
Color.clear
.onChange(of: isDragLocationInsideArea) { oldVal, newVal in
if dragLocation != .zero {
dragInfo = name
}
}
}
}
}
extension View {
@ViewBuilder
func makeDraggable(tag: String, dragLocation: Binding<CGPoint>,
dragTranslation: Binding<CGSize>, dragInfo: Binding<String>, canBeDragged: Bool = true) -> some View {
self
.modifier(DragModifier(tag: tag, dragLocation: dragLocation, dragTranslation: dragTranslation, dragInfo: dragInfo))
.overlay {
if canBeDragged {
Text(tag)
.offset(x: dragTranslation.wrappedValue.width, y: dragTranslation.wrappedValue.height)
.opacity(dragTranslation.wrappedValue != .zero ? 1 : 0)
}
}
}
}
Here dragLocation
is used to track where the user's finger or cursor is, while dragTranslation
is used to make the overlayed text move.
Here's an example on how you can use it:
struct DragView: View {
@State private var dragLocation = CGPoint.zero
/// To make the text move
@State private var dragTranslation = CGSize.zero
/// To keep track of the current cursor location
@State private var dragInfo = " "
/// The text being dragged
@State private var text: String = ""
@State private var startedFrom: Bool = false
var body: some View {
ZStack {
VStack(spacing: 50) {
Text(dragInfo)
Text("Drag me")
.padding()
.frame(width: 100, height: 100)
.makeDraggable(
tag: "Drag Me",
dragLocation: $dragLocation,
dragTranslation: $dragTranslation,
dragInfo: $dragInfo
)
Text("To here")
.frame(width: 100, height: 50)
.makeDraggable(tag: "To Here",
dragLocation: $dragLocation,
dragTranslation: $dragTranslation,
dragInfo: $dragInfo,
canBeDragged: false)
.background(dragInfo == "To Here" ? Color.blue.opacity(0.4) : Color.clear)
.clipShape(.rect(cornerRadius: 4))
Text("You dropped: '\(text)'")
.frame(width: 300, height: 100)
}
}
.gesture(
DragGesture(coordinateSpace: .global)
.onChanged { val in
dragLocation = val.location
print("Drag Info: \(dragInfo) - Drag Trans \(dragTranslation)")
/// Change this to fit your needs
if dragInfo.lowercased().contains("drag me") && dragTranslation == .zero {
startedFrom = true
}
if startedFrom {
dragTranslation = val.translation
print("Drag Trans")
}
}
.onEnded { val in
withAnimation(.smooth) {
if dragInfo.lowercased().contains("to here") && startedFrom {
text = "Drag me"
}
dragTranslation = .zero
}
dragLocation = .zero
dragInfo = " "
startedFrom = false
}
)
}
}
Be aware that it definitely needs some work to make it really usable.
Now it isn't really flexible. Maybe tomorrow I can work on it a bit more.
Here's the result:
![Drag and Drop](https://cdn.statically.io/img/i.sstatic.net/9SDIJ.gif)
Let me know what do you think of this.
.gesture(DragGesture().onChanged({ value in }) .simultaneously(with: TapGesture().onEnded({ _ in })))
?