implement drag and drop
This commit is contained in:
@@ -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 {
|
||||
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)
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 0) {
|
||||
ForEach(viewModel.stopwatches) { stopwatch in
|
||||
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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user