ItemListView+Drag.swift (3024B)
1 import SwiftUI 2 3 extension ItemListView { 4 func handleIOSDragChanged(itemID: UUID, point: CGPoint) { 5 guard let draggedID = draggedItemID, 6 var order = visualOrder, 7 let currentIndex = order.firstIndex(of: draggedID) else { return } 8 9 let draggedFrame = layoutStorage.draggedRowFrame 10 guard draggedFrame != .zero else { return } 11 12 let threshold = draggedFrame.height * 0.2 13 14 // Swap down: finger moved past the bottom edge of the dragged row 15 if currentIndex < order.count - 1 && point.y > draggedFrame.maxY + threshold { 16 order.swapAt(currentIndex, currentIndex + 1) 17 withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { 18 setDragOrder(order) 19 } 20 revealAdjacentItem(order: order, draggedID: draggedID, fingerY: point.y) 21 return 22 } 23 24 // Swap up: finger moved past the top edge of the dragged row 25 if currentIndex > 0 && point.y < draggedFrame.minY - threshold { 26 order.swapAt(currentIndex, currentIndex - 1) 27 withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { 28 setDragOrder(order) 29 } 30 revealAdjacentItem(order: order, draggedID: draggedID, fingerY: point.y) 31 return 32 } 33 34 // No swap, but proactively scroll if finger is in an edge zone 35 revealAdjacentItem(order: order, draggedID: draggedID, fingerY: point.y) 36 } 37 38 private func revealAdjacentItem(order: [UUID], draggedID: UUID, fingerY: CGFloat) { 39 guard let idx = order.firstIndex(of: draggedID) else { return } 40 41 let now = CACurrentMediaTime() 42 guard now - layoutStorage.lastAutoScrollTime > 0.2 else { return } 43 44 let screenHeight = UIScreen.main.bounds.height 45 let edgeZone: CGFloat = 120 46 47 if fingerY > screenHeight - edgeZone, idx + 1 < order.count { 48 layoutStorage.lastAutoScrollTime = now 49 withAnimation(.easeInOut(duration: 0.2)) { 50 scrollPosition.scrollTo(id: order[idx + 1], anchor: .bottom) 51 } 52 } else if fingerY < edgeZone, idx > 0 { 53 layoutStorage.lastAutoScrollTime = now 54 withAnimation(.easeInOut(duration: 0.2)) { 55 scrollPosition.scrollTo(id: order[idx - 1], anchor: .top) 56 } 57 } 58 } 59 60 func commitIOSDrag() { 61 guard let draggedID = draggedItemID, 62 let order = visualOrder, 63 let finalIndex = order.firstIndex(of: draggedID) else { 64 clearDragState() 65 isDragging = false 66 return 67 } 68 do { 69 try store.moveItem(itemID: draggedID, toIndex: finalIndex) 70 clearDragState() 71 isDragging = false 72 } catch { 73 withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { 74 clearDragState() 75 isDragging = false 76 } 77 presentStoreError(error) 78 } 79 } 80 }