implement drag and drop
This commit is contained in:
@@ -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 {
|
||||||
ForEach(viewModel.stopwatches) { stopwatch in
|
LazyVStack(spacing: 0) {
|
||||||
Button {
|
ForEach(viewModel.stopwatches) { stopwatch in
|
||||||
selectedStopwatch = stopwatch
|
StopwatchListItem(
|
||||||
} label: {
|
stopwatch: stopwatch,
|
||||||
StopwatchRow(stopwatch: stopwatch, onDelete: {
|
viewModel: viewModel,
|
||||||
viewModel.deleteStopwatch(id: stopwatch.id)
|
selectedStopwatch: $selectedStopwatch,
|
||||||
})
|
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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user