listless

A simple list app for Apple platforms
Log | Files | Refs | README | LICENSE

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 }