SwiftUI,关于结束、取消拖动操作的通知

问题描述 投票:0回答:1

我已经为一个视图实现了拖放。现在我想在拖动操作开始时更改源视图的状态,并在完成或取消时更改回状态。 下面的示例代码应该说明这一点。

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
),但我不知道操作何时结束。

swiftui drag-and-drop
1个回答
0
投票

这有点晚了,但我正在分享我的解决方案,因为最近没有答案。它使用类似于@FrontFacingWindowCleaner提出的概念,但不使用委托。

实现 isDragActive 的关键概念

  • 此项目使用 SwiftUI 的
    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") }
}
© www.soinside.com 2019 - 2024. All rights reserved.