我已经为一个视图实现了拖放。现在我想在拖动操作开始时更改源视图的状态,并在完成或取消时更改回状态。 下面的示例代码应该说明这一点。
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
}
}
当前实现的问题是,我确实注意到拖动操作何时开始(调用
.onDrag
),但我不知道操作何时结束。
这有点晚了,但我正在分享我的解决方案,因为最近没有答案。它使用类似于@FrontFacingWindowCleaner提出的概念,但不使用委托。
Transferrable
拖放视图修改器 .draggable()
和 .dropDestination()
isDragActive
@State
var 来保存当前的拖动状态isDragActive
环境变量,将当前拖动状态注入到所有子视图中isTargeted:
闭包和所有放置目标 .dropDestination()
视图修饰符来跟踪所有预期放置目标的拖动状态.dropDestination()
视图修饰符,以便在没有瞄准任何预期的放置目标时继续跟踪拖动.onChange(of: isDragActive)
触发所需的操作。FocusState
视图的 List
,以便在拖动过程中选择的背景颜色将发生变化。完整的可构建演示项目位于: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") }
}