implement drag and drop

This commit is contained in:
2026-01-27 02:50:09 +01:00
parent 5f05da552d
commit 101475facc

View File

@@ -7,6 +7,7 @@
import SwiftUI import SwiftUI
import Combine import Combine
import UniformTypeIdentifiers
class ContentViewModel: ObservableObject { class ContentViewModel: ObservableObject {
@Published var stopwatches: [Stopwatch] = [] @Published var stopwatches: [Stopwatch] = []
@@ -35,6 +36,11 @@ class ContentViewModel: ObservableObject {
} }
} }
func moveStopwatch(fromOffsets source: IndexSet, toOffset destination: Int) {
stopwatches.move(fromOffsets: source, toOffset: destination)
save()
}
func save() { func save() {
if let encoded = try? JSONEncoder().encode(stopwatches) { if let encoded = try? JSONEncoder().encode(stopwatches) {
UserDefaults.standard.set(encoded, forKey: saveKey) UserDefaults.standard.set(encoded, forKey: saveKey)
@@ -55,28 +61,23 @@ struct ContentView: View {
@State private var isShowingAddSheet = false @State private var isShowingAddSheet = false
@State private var isShowingSettings = false @State private var isShowingSettings = false
@State private var selectedStopwatch: Stopwatch? @State private var selectedStopwatch: Stopwatch?
@State private var draggingStopwatch: Stopwatch?
@Environment(\.scenePhase) private var scenePhase @Environment(\.scenePhase) private var scenePhase
var body: some View { var body: some View {
NavigationStack { NavigationStack {
List { ScrollView {
LazyVStack(spacing: 0) {
ForEach(viewModel.stopwatches) { stopwatch in ForEach(viewModel.stopwatches) { stopwatch in
Button { StopwatchListItem(
selectedStopwatch = stopwatch stopwatch: stopwatch,
} label: { viewModel: viewModel,
StopwatchRow(stopwatch: stopwatch, onDelete: { selectedStopwatch: $selectedStopwatch,
viewModel.deleteStopwatch(id: stopwatch.id) draggingStopwatch: $draggingStopwatch
}) )
}
.buttonStyle(.plain) // Preserves the row layout and interactions
}
.onDelete { indexSet in
for index in indexSet {
viewModel.deleteStopwatch(at: index)
} }
} }
} }
.listStyle(.plain)
.navigationTitle("MultiChrono") .navigationTitle("MultiChrono")
.toolbar { .toolbar {
ToolbarItem(placement: .topBarLeading) { ToolbarItem(placement: .topBarLeading) {
@@ -126,7 +127,6 @@ struct ContentView: View {
} }
} }
} }
.onChange(of: scenePhase) { newPhase in .onChange(of: scenePhase) { newPhase in
if newPhase == .background || newPhase == .inactive { if newPhase == .background || newPhase == .inactive {
viewModel.save() viewModel.save()
@@ -135,6 +135,69 @@ struct ContentView: View {
} }
} }
struct StopwatchListItem: View {
@ObservedObject var stopwatch: Stopwatch
@ObservedObject var viewModel: ContentViewModel
@Binding var selectedStopwatch: Stopwatch?
@Binding var draggingStopwatch: Stopwatch?
var body: some View {
Button {
selectedStopwatch = stopwatch
} label: {
StopwatchRow(stopwatch: stopwatch, onDelete: {
withAnimation {
viewModel.deleteStopwatch(id: stopwatch.id)
}
})
}
.buttonStyle(.plain)
.padding(.horizontal)
.padding(.vertical, 8)
.background(Color(UIColor.systemBackground))
.contentShape(Rectangle())
.onDrag {
self.draggingStopwatch = stopwatch
return NSItemProvider(object: stopwatch.id.uuidString as NSString)
}
.onDrop(of: [UTType.text], delegate: StopwatchDropDelegate(item: stopwatch, viewModel: viewModel, draggingItem: $draggingStopwatch))
Divider()
.padding(.leading)
}
}
struct StopwatchDropDelegate: DropDelegate {
let item: Stopwatch
let viewModel: ContentViewModel
@Binding var draggingItem: Stopwatch?
func dropEntered(info: DropInfo) {
guard let draggingItem = draggingItem else { return }
if draggingItem.id != item.id {
if let from = viewModel.stopwatches.firstIndex(where: { $0.id == draggingItem.id }),
let to = viewModel.stopwatches.firstIndex(where: { $0.id == item.id }) {
withAnimation {
viewModel.stopwatches.move(fromOffsets: IndexSet(integer: from),
toOffset: to > from ? to + 1 : to)
}
}
}
}
func performDrop(info: DropInfo) -> Bool {
viewModel.save()
draggingItem = nil
return true
}
func dropUpdated(info: DropInfo) -> DropProposal? {
return DropProposal(operation: .move)
}
}
#Preview { #Preview {
ContentView() ContentView()
} }