Die horizontale Drag-to-Reorder-Ansicht von SwiftUI fühlt sich nervös an – wie kann ich das Ziehen reibungslos gestaltenIOS

Programmierung für iOS
Anonymous
 Die horizontale Drag-to-Reorder-Ansicht von SwiftUI fühlt sich nervös an – wie kann ich das Ziehen reibungslos gestalten

Post by Anonymous »

Ich erstelle in SwiftUI eine horizontale Ansicht im „Karussell“-Stil, in der Elemente durch Ziehen neu angeordnet werden können (ähnlich einer Editor-Timeline).
Die Grundidee:
Elemente werden in einer horizontalen Spur innerhalb einer ScrollView (.horizontal) angeordnet.
Im Bearbeitungsmodus können Sie ein Element horizontal ziehen.
Während des Ziehens aktualisiere ich das Array und Berechnen Sie alle Artikelpositionen neu, um sie live neu anzuordnen.
Der folgende Code ist ein Minimalbeispiel. Funktionell funktioniert es, aber das Ziehen fühlt sich nicht gleichmäßig an:
Das gezogene Element zittert und die anderen Elemente springen herum, während ich ziehe.
Meine Frage ist:
Welche Änderungen sollte ich an dieser Implementierung vornehmen, damit sich die Drag-to-Reorder-Interaktion beim horizontalen Ziehen von Elementen reibungslos und kontinuierlich (kein Zittern) anfühlt?
Automatischer Bildlauf beim horizontalen Ziehen von Zellen in der Nähe der Ränder Scrollende Leinwand
Hier ist der Democode:

Code: Select all

struct ContentView: View {
@State var isEditing: Bool = false
var body: some View {
VStack(alignment: .center, spacing: 30){
Button( isEditing ? "Done" : "Edit", systemImage: isEditing ? "checkmark" : "slider.horizontal.3") {
isEditing.toggle()
}
.buttonStyle(.borderedProminent)
HorizontalCanvasView(editMode: $isEditing)
}
.frame(maxHeight: .infinity)

}
}
struct CanvasItem: Identifiable, Equatable {
let id = UUID()
var color: Color
var size: CGSize = CGSize(width: 100, height: 100)
}
struct HorizontalCanvasView: View {
@State private var items: [CanvasItem] = [
CanvasItem(color: .blue),
CanvasItem(color: .red),
CanvasItem(color: .green)
]

@Binding var editMode: Bool

private let canvasHeight: CGFloat = 300
private let itemSize = CGSize(width: 100, height: 100)
private let itemSpacing: CGFloat = 10
private let horizontalPadding: CGFloat = 200

// Drag state
@State private var draggedID: UUID? = nil
@State private var dragOffsetX: CGFloat = 0          // visual offset for dragged item
@State private var lastTranslationX: CGFloat = 0     // last gesture.translation.width

private var cellWidth: CGFloat {
itemSize.width + itemSpacing
}

var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
ZStack(alignment: .topLeading) {
Rectangle()
.fill(Color.gray.opacity(0.1))
.frame(height: canvasHeight)

HStack(spacing: itemSpacing) {
ForEach(items) { item in
CanvasItemView(
item: item,
size: itemSize,
isDragging: draggedID == item.id,
editMode: editMode,
horizontalOffset: draggedID == item.id ? dragOffsetX : 0,
onDragChanged: { value in
handleDragChanged(for: item, value: value)
},
onDragEnded: { value in
handleDragEnded(for: item, value: value)
}
)
}
}
.padding(.horizontal, horizontalPadding)
.frame(height: canvasHeight)
}
}
.frame(height: canvasHeight)
.onChange(of: editMode) { oldValue, newValue in
if !newValue {
// Reset drag state when leaving edit mode
draggedID = nil
dragOffsetX = 0
lastTranslationX = 0
}
}
}

// MARK: - Drag logic (smooth, order-based)

private func handleDragChanged(for item: CanvasItem, value: DragGesture.Value) {
guard editMode else { return }

// Start of drag
if draggedID == nil {
draggedID = item.id
dragOffsetX = 0
lastTranslationX = value.translation.width
return
}

// Only track the currently dragged item
guard draggedID == item.id,
let currentIndex = items.firstIndex(where: { $0.id == item.id }) else { return }

// Incremental delta instead of full translation
let deltaX = value.translation.width - lastTranslationX
lastTranslationX = value.translation.width
dragOffsetX += deltaX

// Move right
if dragOffsetX > cellWidth / 2, currentIndex <  items.count - 1 {
let newIndex = currentIndex + 1
withAnimation(.spring(response: 0.25, dampingFraction: 0.9)) {
items.move(fromOffsets: IndexSet(integer: currentIndex),
toOffset: newIndex + 1)
}
// Keep visual position continuous
dragOffsetX -= cellWidth
}
// Move left
else if dragOffsetX < -cellWidth / 2, currentIndex > 0 {
let newIndex = currentIndex - 1
withAnimation(.spring(response: 0.25, dampingFraction: 0.9)) {
items.move(fromOffsets: IndexSet(integer: currentIndex),
toOffset: newIndex)
}
dragOffsetX += cellWidth
}
}

private func handleDragEnded(for item: CanvasItem, value: DragGesture.Value) {
guard draggedID == item.id else { return }

withAnimation(.spring(response: 0.3, dampingFraction: 0.9)) {
dragOffsetX = 0
}
draggedID = nil
lastTranslationX = 0
}
}

struct CanvasItemView: View {
let item: CanvasItem
let size: CGSize
let isDragging: Bool
let editMode: Bool
let horizontalOffset: CGFloat
let onDragChanged: (DragGesture.Value) -> Void
let onDragEnded: (DragGesture.Value) -> Void

var body: some View {
ZStack {
Rectangle()
.fill(item.color.opacity(isDragging ? 0.7 : 0.8))
.frame(width: size.width, height: size.height)
.overlay(
Text("Item \(item.id.uuidString.prefix(4))")
.foregroundColor(.white)
)
.scaleEffect(isDragging ? 1.05 : 1.0)
.shadow(color: .black.opacity(0.25),
radius: isDragging ? 10 : 0,
x: 0,
y: isDragging ? 6 : 0)

if editMode {
VStack {
Spacer()
Image(systemName: "line.3.horizontal")
.font(.title2)
.foregroundColor(.black)
.padding(.bottom, 8)
}
.frame(width: size.width)
}
}
.offset(x: horizontalOffset, y: 0)
.zIndex(isDragging ? 1 : 0)
.gesture(
editMode ?
DragGesture()
.onChanged(onDragChanged)
.onEnded(onDragEnded)
: nil
)
}
}

Gefällt mir
Image

Irgendwelche Vorschläge, wie man die Daten/Gesten so strukturieren kann, dass sich das Ziehen und Neuanordnen reibungslos anfühlt?

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post