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 Combine
import UniformTypeIdentifiers
class ContentViewModel: ObservableObject {
@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() {
if let encoded = try? JSONEncoder().encode(stopwatches) {
UserDefaults.standard.set(encoded, forKey: saveKey)
@@ -55,28 +61,23 @@ struct ContentView: View {
@State private var isShowingAddSheet = false
@State private var isShowingSettings = false
@State private var selectedStopwatch: Stopwatch?
@State private var draggingStopwatch: Stopwatch?
@Environment(\.scenePhase) private var scenePhase
var body: some View {
NavigationStack {
List {
ScrollView {
LazyVStack(spacing: 0) {
ForEach(viewModel.stopwatches) { stopwatch in
Button {
selectedStopwatch = stopwatch
} label: {
StopwatchRow(stopwatch: stopwatch, onDelete: {
viewModel.deleteStopwatch(id: stopwatch.id)
})
}
.buttonStyle(.plain) // Preserves the row layout and interactions
}
.onDelete { indexSet in
for index in indexSet {
viewModel.deleteStopwatch(at: index)
StopwatchListItem(
stopwatch: stopwatch,
viewModel: viewModel,
selectedStopwatch: $selectedStopwatch,
draggingStopwatch: $draggingStopwatch
)
}
}
}
.listStyle(.plain)
.navigationTitle("MultiChrono")
.toolbar {
ToolbarItem(placement: .topBarLeading) {
@@ -126,7 +127,6 @@ struct ContentView: View {
}
}
}
.onChange(of: scenePhase) { newPhase in
if newPhase == .background || newPhase == .inactive {
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 {
ContentView()
}