I have implemented drag and drop for one view. Now I want to change the state of the source view at the start of a drag operation and change the state back at completion or cancellation. The following example code should illustrate this.
import UniformTypeIdentifiers
struct DragableView: View {
@State var isActiveDropTarget: Bool = false
@State var isDragged: Bool = false
var body: some View {
Text("Hello world")
.overlay(RoundedRectangle(cornerRadius: 8)
.fill(Color.accentColor.opacity(0.1))
.opacity(isActiveDropTarget ? 1.0 : 0.0))
.opacity(isDragged ? 0.5 : 1)
.onDrag {
isDragged = true
return NSItemProvider(object: "Test" as NSString)
}
.onDrop(of: [UTType.data], delegate: self)
}
}
extension DragableView: DropDelegate
{
func dropEntered(info: DropInfo)
{
isActiveDropTarget = true
}
func dropExited(info: DropInfo)
{
isActiveDropTarget = false
}
func performDrop(info: DropInfo) -> Bool
{
isActiveDropTarget = false
/* Handle drop
...
*/
return true
}
}
The problem with the current implementation is that I do notice when a drag operation starts (.onDrag is called), but I don't know when the operation ends.
This is a bit late, but I'm sharing my solution because there is no recent answer. It uses a concept similar to that proposed by @FrontFacingWindowCleaner, but does not use Delegates.
Transferrable drag and drop View modifiers .draggable() and .dropDestination()isDragActive @State var to hold the current dragging stateisDragActive environment varable to inject the current dragging state into all child ViewsisTargeted: closure with all of the drop target .dropDestination() view modifiers to track dragging status for all expected drop targets.dropDestination() view modifier on an outer View in order to continue tracking the drag when none of the expected drop targets is being targeted.onChange(of: isDragActive) to trigger desired actions when dragging begins or ends anywhere within the outer View.FocusState of the List view so the background color of a selection will change during a drag.A complete buildable demo project is at: https://github.com/Whiffer/SwiftUI_isDragActive
import SwiftUI
import UniformTypeIdentifiers
struct ContentView: View {
@State private var selection = Set<Node>()
@FocusState private var isListFocused: Bool
@State private var isDragActive = false // <<<<
var body: some View {
VStack {
List(nodes, id: \.self , children: \.children, selection: self.$selection) { node in
NodeView(node: node)
}
.focused($isListFocused)
Text("isDragActive: \(isDragActive.description)")
}
.dropDestination(for: Node.self) { _, _ in
return false
} isTargeted: { isDragActive = $0 } // <<<<
.onChange(of: isDragActive) { _, newValue in
if newValue == true {
print("Drag started")
isListFocused = false
}
if newValue == false {
print("Drag ended")
isListFocused = true
}
}
.environment(\.isDragActive, $isDragActive) // <<<<
}
}
struct NodeView: View {
var node: Node
@State private var isTargeted = false
@Environment(\.isDragActive) private var isDragActive // <<<<
var body: some View {
Text(node.name)
.listRowBackground(RoundedRectangle(cornerRadius: 5, style: .circular)
.padding(.horizontal, 10)
.foregroundColor(isTargeted ? Color(nsColor: .selectedContentBackgroundColor) : Color.clear)
)
.draggable(node)
.dropDestination(for: Node.self) { droppedNodes, _ in
for droppedNode in droppedNodes {
print("\(droppedNode.name) dropped on: \(node.name)")
}
return true
} isTargeted: {
isTargeted = $0
isDragActive.wrappedValue = $0 // <<<<
}
}
}
struct DragActive: EnvironmentKey { // <<<<
static var defaultValue: Binding<Bool> = .constant(false)
}
extension EnvironmentValues {
var isDragActive: Binding<Bool> { // <<<<
get { self[DragActive.self] }
set { self[DragActive.self] = newValue }
}
}
let nodes: [Node] = [
.init(name: "Clothing", children: [
.init(name: "Hoodies"), .init(name: "Jackets"), .init(name: "Joggers"), .init(name: "Jumpers"),
.init(name: "Jeans", children: [.init(name: "Regular", children: [.init(name: "Size 34"), .init(name: "Size 32"), ] ), .init(name: "Slim") ] ), ] ),
.init(name: "Shoes", children: [.init(name: "Boots"), .init(name: "Sandals"), .init(name: "Trainers"), ] ),
.init(name: "Socks", children: [.init(name: "Crew"), .init(name: "Dress"), .init(name: "Athletic"), ] ),
]
struct Node: Identifiable, Hashable, Codable {
var id = UUID()
var name: String
var children: [Node]? = nil
}
extension Node: Transferable {
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .node)
}
}
extension UTType {
// Add a "public.data" Exported Type Identifier for this on the Info tab for the Target's Settings
static var node: UTType { UTType(exportedAs: "com.experiment.node") }
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With