You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI:LazyVGrid拖拽元素外部释放后状态异常解决方案咨询

解决SwiftUI拖拽元素到视图外时状态无法重置的问题

问题分析

当前实现仅在元素拖入带.dropDestination的视图时才会重置draggingItem状态,但拖到视图外部(如窗口外、屏幕边缘)时,SwiftUI原生拖放修饰符不会触发回调,导致draggingItem一直处于“拖拽中”状态。

解决方案

通过添加全局拖拽结束监听器,捕获任何位置的元素释放动作,以下是适配iOS和macOS的实现方案:

步骤1:创建拖拽结束监听器

针对iOS:

import SwiftUI
import UIKit

struct DragEndListener: UIViewRepresentable {
    var onDragEnd: () -> Void
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        view.isUserInteractionEnabled = true
        let panGesture = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handlePan(_:)))
        panGesture.delegate = context.coordinator
        view.addGestureRecognizer(panGesture)
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(onDragEnd: onDragEnd)
    }
    
    class Coordinator: NSObject, UIGestureRecognizerDelegate {
        var onDragEnd: () -> Void
        
        init(onDragEnd: @escaping () -> Void) {
            self.onDragEnd = onDragEnd
        }
        
        @objc func handlePan(_ gesture: UIPanGestureRecognizer) {
            if gesture.state == .ended || gesture.state == .cancelled {
                onDragEnd()
            }
        }
        
        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
            true
        }
    }
}

针对macOS:

import SwiftUI
import AppKit

struct DragEndListener: NSViewRepresentable {
    var onDragEnd: () -> Void
    
    func makeNSView(context: Context) -> NSView {
        let view = NSView()
        view.wantsLayer = true
        let panGesture = NSPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handlePan(_:)))
        view.addGestureRecognizer(panGesture)
        return view
    }
    
    func updateNSView(_ nsView: NSView, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(onDragEnd: onDragEnd)
    }
    
    class Coordinator: NSObject {
        var onDragEnd: () -> Void
        
        init(onDragEnd: @escaping () -> Void) {
            self.onDragEnd = onDragEnd
        }
        
        @objc func handlePan(_ gesture: NSPanGestureRecognizer) {
            if gesture.state == .ended || gesture.state == .cancelled {
                onDragEnd()
            }
        }
    }
}

步骤2:在主视图中集成监听器

修改MainView,用ZStack将监听器覆盖全屏,同时设置allowsHitTesting(false)避免影响原有交互:

struct MainView: View {
    let columns = [
        GridItem(.fixed(160)),
        GridItem(.fixed(160))
    ]
    @State private var items = (0..<20).map { ItemDraggable(id: $0) }
    @State private var draggingItem: ItemDraggable?
    
    var body: some View {
        ZStack {
            ScrollView {
                Text("Items, dragging: \(draggingItem?.id ?? -1)")
                LazyVGrid(columns: columns) {
                    ForEach(items) { item in
                        DraggableView(item: item, draggingItem: $draggingItem)
                    }
                }
            }
            .background(Color.white)
            .dropDestination(for: ItemDraggable.self) { items, location in
                draggingItem = nil
                return true
            }
            
            // 全局拖拽结束监听器
            DragEndListener {
                draggingItem = nil
            }
            .allowsHitTesting(false)
            .edgesIgnoringSafeArea(.all)
        }
    }
}

额外优化

将draggable的状态设置逻辑从预览视图的onAppear改为原生拖拽开始回调(iOS 16+/macOS 13+支持),更可靠:

.draggable(item, preview: {
    Text("Dragging item \(item.id)")
}) {
    draggingItem = item
}

原理说明

全屏手势监听器会捕获拖拽手势的ended或cancelled状态,无论释放位置在哪里。allowsHitTesting(false)确保监听器不会拦截其他视图的交互逻辑,仅监听手势状态变化。

内容的提问来源于stack exchange,提问作者Ludvig Kratz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 22:25:21