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

能否通过SwiftUI的OnDrag与OnDrop精准修改视图偏移量?

解决OnDrag & OnDrop获取拖拽起始位置的问题

要在SwiftUI的OnDrag/OnDrop流程中获取首次触摸的起始位置,核心思路是用低阈值手势提前捕获起始点,再通过拖拽数据传递给接收端,以下是具体实现方案:

步骤1:捕获拖拽起始位置

在需要拖拽的视图上添加DragGesture(minimumDistance: 0),这个手势会在用户触摸的瞬间触发,我们可以用它记录首次触摸的startLocation:

struct DraggableView: View {
    @State private var dragStartLocation: CGPoint?
    
    var body: some View {
        Rectangle()
            .frame(width: 100, height: 100)
            .foregroundColor(.blue)
            // 捕获起始位置的手势
            .gesture(
                DragGesture(minimumDistance: 0)
                    .onChanged { value in
                        // 仅在首次触摸时记录起始位置
                        if dragStartLocation == nil {
                            dragStartLocation = value.startLocation
                        }
                    }
                    .onEnded { _ in
                        // 拖拽结束后重置,避免影响下一次拖拽
                        dragStartLocation = nil
                    }
            )
            // 绑定OnDrag逻辑
            .onDrag {
                // 把起始位置和拖拽内容一起打包传递
                guard let startLoc = dragStartLocation else {
                    return NSItemProvider(object: "DefaultDragItem" as NSString)
                }
                // 用自定义UTI传递结构化数据
                let dragData = DragData(content: "MyDraggedItem", startLocation: startLoc)
                return NSItemProvider(object: dragData)
            }
    }
}

步骤2:定义可传递的拖拽数据结构

用Transferable协议(iOS 16+)简化数据传递,定义包含拖拽内容和起始位置的结构体:

import UniformTypeIdentifiers

struct DragData: Transferable {
    let content: String
    let startLocation: CGPoint
    
    static var transferRepresentation: some TransferRepresentation {
        CodableRepresentation(for: DragData.self, contentType: .customDragData)
    }
    
    // 自定义UTI,确保数据唯一识别
    static let customDragData = UTType(exportedAs: "com.yourapp.customdragdata")
}

步骤3:接收端处理拖拽数据

在接收视图的DropDelegate中,提取传递过来的起始位置,结合当前拖拽位置计算偏移量:

struct DropZoneView: View {
    @State private var dragOffset: CGSize = .zero
    
    var body: some View {
        Rectangle()
            .frame(width: 300, height: 300)
            .foregroundColor(.gray.opacity(0.3))
            .onDrop(of: [DragData.customDragData], delegate: DropHandler(offset: $dragOffset))
    }
}

class DropHandler: DropDelegate {
    @Binding var dragOffset: CGSize
    private var initialStartLocation: CGPoint?
    
    init(offset: Binding<CGSize>) {
        _dragOffset = offset
    }
    
    func dropEntered(info: DropInfo) {
        // 从拖拽数据中提取起始位置
        let itemProvider = info.itemProviders(for: [DragData.customDragData]).first
        itemProvider?.loadObject(ofClass: DragData.self) { data, error in
            guard let dragData = data as? DragData else { return }
            DispatchQueue.main.async {
                self.initialStartLocation = dragData.startLocation
            }
        }
    }
    
    func performDrop(info: DropInfo) -> Bool {
        // 计算偏移量:当前拖拽位置 - 起始位置
        guard let startLoc = initialStartLocation else { return false }
        let currentDragLocation = info.location
        dragOffset = CGSize(
            width: currentDragLocation.x - startLoc.x,
            height: currentDragLocation.y - startLoc.y
        )
        return true
    }
    
    func dropExited(info: DropInfo) {
        // 退出拖拽区域后重置起始位置
        initialStartLocation = nil
    }
}

关键说明

  • DragGesture(minimumDistance: 0)不会和OnDrag冲突,因为OnDrag的拖拽优先级更高,但这个低阈值手势会先触发,帮我们捕获到起始点
  • 自定义UTI是为了避免和系统默认的拖拽类型混淆,确保我们能精准拿到自己传递的起始位置数据
  • 异步加载数据后要切换到主队列更新UI状态,避免线程问题

内容的提问来源于stack exchange,提问作者Mohammed Noor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 16:02:05