← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


ListlessiOS/Extensions/ItemListView+Drag.swift (3024B)

1import SwiftUI
2
3extension 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}