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

SwiftUI非List视图拖拽重排:如何让拖拽预览与原视图一致

SwiftUI拖拽重排(非List组件):修复拖拽预览异常问题

要实现拖拽预览与原视图完全一致,核心是自定义拖拽预览样式,覆盖系统默认的容器装饰,同时补全拖拽重排的完整逻辑(原代码仅实现了拖拽触发,缺少放置处理)。

问题根源

默认的.draggable预览会被系统添加默认的圆角背景和尺寸约束,导致预览与原视图的宽高、样式不匹配。

完整解决方案

import SwiftUI

struct DragReorderView: View {
    @State private var items: [Color] = [.red, .blue, .green, .orange, .purple]
    @State private var draggingItem: Color?
    
    var body: some View {
        VStack(spacing: 16) {
            ForEach(items, id: \.self) { color in
                color
                    .frame(height: 100)
                    .cornerRadius(8) // 原视图的样式,预览需同步
                    // 自定义拖拽预览,完全匹配原视图
                    .draggable(color) {
                        color
                            .frame(height: 100)
                            .cornerRadius(8)
                            .frame(maxWidth: .infinity) // 强制宽度与父容器一致
                            .shadow(radius: 6) // 模拟拖拽悬浮效果(可选)
                            .opacity(0.9) // 增加透明度提升自然感(可选)
                    }
                    // 处理放置逻辑,实现重排
                    .dropDestination(for: Color.self) { _, location in
                        guard let draggedItem = draggingItem, 
                              let fromIndex = items.firstIndex(of: draggedItem) 
                        else { return false }
                        
                        // 计算放置目标位置
                        let toIndex = items.firstIndex { item in
                            let itemFrame = getFrame(for: item)
                            return location.y < itemFrame.midY
                        } ?? items.count
                        
                        // 执行重排动画
                        if fromIndex != toIndex {
                            withAnimation {
                                items.move(
                                    fromOffsets: IndexSet(integer: fromIndex),
                                    toOffset: toIndex > fromIndex ? toIndex - 1 : toIndex
                                )
                            }
                        }
                        draggingItem = nil
                        return true
                    }
                    // 跟踪当前拖拽的元素
                    .onDrag {
                        draggingItem = color
                        return NSItemProvider(object: color.description as NSString)
                    }
            }
        }
        .padding()
    }
    
    // 计算视图在全局坐标系中的位置,用于判断放置位置
    private func getFrame(for color: Color) -> CGRect {
        guard let index = items.firstIndex(of: color) else { return .zero }
        let itemHeight: CGFloat = 100
        let spacing: CGFloat = 16
        let padding: CGFloat = 16
        return CGRect(
            x: padding,
            y: padding + index * (itemHeight + spacing),
            width: UIScreen.main.bounds.width - padding * 2,
            height: itemHeight
        )
    }
}

关键细节说明

  1. 预览匹配:拖拽预览完全复制原视图的frame、cornerRadius等样式,通过maxWidth: .infinity确保宽度与原视图一致,避免系统默认的窄化效果。
  2. 重排逻辑:通过dropDestination监听放置事件,结合draggingItem跟踪拖拽元素,计算放置位置后调用move(fromOffsets:toOffset:)实现数组重排,配合withAnimation添加平滑过渡。
  3. 注意事项:如果存在重复颜色,建议改用自定义Identifiable类型,避免firstIndex(of:)匹配错误:
struct ColoredItem: Identifiable {
    let id = UUID()
    let color: Color
}

内容的提问来源于stack exchange,提问作者Nicolas Gimelli

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 10:46:28