SwiftUI中draggable与dropDestination拖拽网格单元格动画失效问题
SwiftUI网格单元格拖拽移动:过渡动画失效问题
我在SwiftUI中结合draggable与dropDestination实现网格单元格的拖拽移动功能,目前功能逻辑正常,但拖拽过程中的过渡动画完全不生效。
相关代码
struct PhotosGridViewDraggable: View { @State var draggedItem: Color? @State var colors: [Color] = [.red, .blue, .gray, .green, .pink, .yellow] @Namespace var animation var body: some View { GeometryReader { proxy in let hGap: CGFloat = 14 let width = proxy.size.width - (hGap * 2) let cellWidth = (width / 3) let cellSize = CGSize(width: cellWidth, height: cellWidth * 1.5) let columns = [GridItem(.fixed(cellWidth)), GridItem(.fixed(cellWidth)), GridItem(.fixed(cellWidth))] LazyVGrid(columns: columns) { ForEach(colors.indices, id: \.self) { index in let color = colors[index] Rectangle() .fill(color) .frame(width: cellSize.width, height: cellSize.height) .draggable(color, preview: { RoundedRectangle(cornerRadius: 10) .fill(.ultraThinMaterial) .frame(width: 1, height: 1) .onAppear { draggedItem = color } }) .dropDestination(for: Color.self, action: { items, location in return false }, isTargeted: { status in if let draggedItem = draggedItem, status, draggedItem != color { if let sourceIndex = colors.firstIndex(of: draggedItem), let destinationIndex = colors.firstIndex(of: color) { withAnimation { // 此处动画无效果 let sourceItem = colors.remove(at: sourceIndex) colors.insert(sourceItem, at: destinationIndex) } } } }) } } } } }
效果对比
- 预期效果:拖拽单元格时,其他单元格会平滑过渡到新位置,形成连贯的动画。
- 实际效果:拖拽完成后,单元格直接跳转到目标位置,没有任何过渡动画。
解决方案
问题根源主要有两点:
- 使用
ForEach(colors.indices, id: \.self)绑定索引,当数组元素移动时,SwiftUI无法正确识别视图的身份,导致动画无法触发。 - 在
dropDestination的isTargeted闭包中修改数据源,这个闭包的触发时机不适合处理数据变更,且Color作为值类型,用firstIndex(of:)存在潜在的重复元素识别问题。
修改步骤:
- 创建带唯一标识的数据结构体,替代直接使用
Color。 - 将数据源改为带唯一ID的数组类型,确保每个元素有稳定的身份标识。
- 修改
ForEach绑定元素的id,让SwiftUI能跟踪每个视图的身份。 - 在
dropDestination的action闭包中处理数据交换,配合withAnimation实现平滑过渡。 - 优化拖拽预览样式,提升交互体验。
修改后的完整代码
struct PhotosGridViewDraggable: View { @State private var draggedItem: GridColor? @State private var colors: [GridColor] = [ .init(color: .red), .init(color: .blue), .init(color: .gray), .init(color: .green), .init(color: .pink), .init(color: .yellow) ] var body: some View { GeometryReader { proxy in let hGap: CGFloat = 14 let width = proxy.size.width - (hGap * 2) let cellWidth = width / 3 let cellSize = CGSize(width: cellWidth, height: cellWidth * 1.5) let columns = Array(repeating: GridItem(.fixed(cellWidth)), count: 3) LazyVGrid(columns: columns, spacing: hGap) { ForEach($colors) { $colorItem in Rectangle() .fill(colorItem.color) .frame(width: cellSize.width, height: cellSize.height) .cornerRadius(10) .draggable(colorItem) { // 自定义拖拽预览 Rectangle() .fill(colorItem.color) .frame(width: cellSize.width, height: cellSize.height) .cornerRadius(10) .shadow(radius: 8) } .dropDestination(for: GridColor.self) { items, location in guard let draggedItem = items.first, draggedItem.id != colorItem.id else { return false } withAnimation(.easeInOut) { if let sourceIndex = colors.firstIndex(where: { $0.id == draggedItem.id }), let destIndex = colors.firstIndex(where: { $0.id == colorItem.id }) { let item = colors.remove(at: sourceIndex) // 处理插入位置的偏移(源索引小于目标时需要减1) colors.insert(item, at: sourceIndex < destIndex ? destIndex - 1 : destIndex) } } return true } } } .padding(.horizontal, hGap) } } } struct GridColor: Identifiable { let id = UUID() let color: Color }
内容的提问来源于stack exchange,提问作者tintin
相关产品推荐
相关产品推荐

