Drag’N’Dropで配列に要素を追加したり、要素を削除したり、要素の順番を変える方法を探る。以下のコードではalart Dialogの出し方も記載されている。
SwiftUI TableでRowをDrag&Dropする SmallDeskSoftware

コード解説
要素の移動、削除の際に呼び出されるonMove, onDeleteに引数で渡ってくる値、引数に渡さないといけない値は全く異なる。同じにしておいてくれれば良いのだが。onDeleteでは削除後にCallbackされるメソッドを引数に与える。メソッドにセットされる引数はListのIndex番号(IndexSet型)だ。onDeleteはListのCellを2本指Swipeすれば呼び出せるのだが、ContextMenuを使う方が簡単だ。ContextMenuはListにセットするのではなくListに表示されるCellにセットする。
要素の追加にはalart Dialogを使うのだが、ListにセットしてもListを格納したVStackにセットしても機能するようだ。alart Dialog表示/非表示の制御にはView状態管理変数@State変数を使えば良い。alart Dialogで受け取ったItem構造体のメンバー(タイトル、値)がViewに伝わる様にView状態管理変数@Stateが同じく使われる。
追加によって要素がWindow Sizeを超えて表示されない場合でもScroll Barが自動で付加されるのが嬉しい。
import SwiftUI
struct Item: Identifiable, Equatable {
let id = UUID()
let title:String
let value: Int
}
struct ContentView: View {
@State var listItems = [Item(title: "Item1", value: 9), Item(title: "Item2", value: 12), Item(title: "Item3", value: 15)]
@State private var showingAddDialog = false
@State private var newTitle: String = ""
@State private var newValueText: String = ""
var body: some View {
VStack {
List {
ForEach(listItems, id: \.id) { item in
HStack {
Text(item.title)
Text("\(item.value)")
}
.frame(height: 30)
.contextMenu {
Button(role: .destructive) {
if let index = listItems.firstIndex(of: item) {
listItems.remove(at: index)
}
} label: {
Label("Delete", systemImage: "trash")
}
}
}
.onMove { indexSet, dest in
listItems.move(fromOffsets: indexSet, toOffset: dest)
}
.onDelete(perform: deleteItems)
}
.animation(.default, value: listItems)
.toolbar {
Button {
newTitle = ""
newValueText = ""
showingAddDialog = true
} label: {
Image(systemName: "plus")
}
}
.navigationTitle("Items")
.alert("Add Item", isPresented: $showingAddDialog) {
TextField("Title", text: $newTitle)
TextField("Value", text: $newValueText)
Button("Add") {
if let intValue = Int(newValueText.trimmingCharacters(in: .whitespacesAndNewlines)), !newTitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
listItems.append(Item(title: newTitle, value: intValue))
}
}
Button("Cancel", role: .cancel) { }
} message: {
Text("Enter a title and a numeric value.")
}
}
}
func deleteItems(at offsets: IndexSet) {
listItems.remove(atOffsets: offsets)
}
}
