Sponsor Link
環境&対象
- macOS14.6
- Xcode 16.1 Beta
- iOS 17.5
- Swift 5.9
Table
要素を表形式で表示する時に使用する View です。
以前の記事で説明しています。
[SwiftUI][macOS] Table の使い方(2)
Drag&Drop による並び替え
表の一部を Drag&Drop で入れ替えるような UI は、以前からよく使用されています。
以下の例はすべて、同一リスト内から 行を入れ替えるための Drag&Drop を想定しています。
リスト外から要素を受け取ることを想定していません。
List での 並び替え(1)
SwiftUI の List では、非常に簡単に実装できるようになっています。
具体的には、onMove を使用して実装します。
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)]
var body: some View {
VStack {
List {
ForEach(listItems) { item in
HStack {
Text(item.title)
Text("\(item.value)")
}
}
.onMove { indexSet, dest in
listItems.move(fromOffsets: indexSet, toOffset: dest)
}
}
.animation(.default, value: listItems)
}
.padding()
}
}
onMove を指定しただけで、以下のように動作するようになります。animation を指定すると スムーズに見える動きになります。
List での並び替え(2)
onMove ではない方法も使用できます。
.draggable と .dropDestination を組み合わせる方法です。
参考
draggable(_:)Apple Developer Documentation
参考
dropDestination(for:action:isTargeted:)Apple Developer Documentation
特に List の内部だけではなく、通常(?) の Drag&Drop でも使用されます。
以前、以下の記事で説明しました。
[Swift][SwiftUI] Transferable で実装する Drag&Drop
これを List の行要素に対しても使用します。
まずは、List の要素である Item を Transferable に準拠させる必要があります。
また、Drag&Drop の要素を指定するために、UTType も定義する必要があります。
extension UTType {
static var item = UTType(exportedAs: "com.smalldesksoftware.item")
} // note: Info.plist にも追加しないとワーニングが出ます
struct Item: Identifiable, Equatable, Codable { // Codable を追加して Transferable にしました
var id = UUID()
let title:String
let value: Int
}
extension Item: Transferable {
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(for: Item.self, contentType: .item)
}
}
Item を Codable に conform させることで、CodableRepresentation を使用することが可能になることを利用して Transferable に準拠させています。
UTType については、以下の記事の中で解説しています。
[Swift] [iOS][MacOS] Document App を作る(その1:UTI と Document Type の定義)
Drag&Drop する要素を Transferable に conform させ、UTType も定義しましたので、
draggable と dropDestination を使う準備ができました。
やることは以下です。
・Drag を開始する要素に draggable を指定して、ドラッグする要素を渡す
・Drop する対象に、dropDestination を指定して、Drop 時の動作を記述する
実装すると、以下のようになります。
struct ContentView: View {
@State var listItems = [Item(title: "Item1", value: 9), Item(title: "Item2", value: 12), Item(title: "Item3", value: 15)]
var body: some View {
VStack {
List {
ForEach(listItems) { item in
HStack {
Text(item.title)
Text("\(item.value)")
}
// draggable で Drag 時の要素を指定する
.draggable(item)
}
// dropDestination で Drop を受け取る items は、[Item] 型、index は、Int 型
.dropDestination(for: Item.self, action: { items, index in
var indexSet = IndexSet()
items.forEach({ item in
if let index = listItems.firstIndex(of: item) {
indexSet.insert(index)
}
})
// onMove と同様に、move で要素を移動する
listItems.move(fromOffsets: indexSet, toOffset: index)
})
}
.animation(.default, value: listItems)
}
.padding()
}
}
動作は、onMove で実装したものと同じになります。
ForEach が必要
気をつける点として、dropDestination は、ForEach に付与できる ViewModifier である点です。
したがって、以下のような 表示データを List の引数として渡している場合には使用できません。
List(listItems) { item in
Text(item.title)
}
Table での並び替え
Table では、onMove を使用できません。
List での2つ目の例で使用した draggable/ dropDestination を使用することになります。
# 厳密には、同じ関数ではありません。(引数の順番も異なります)
やることも List の場合とほとんど同じです。
・Drag を開始する要素に draggable を指定して、ドラッグする要素を渡す
・Drop する対象に、dropDestination を指定して、Drop 時の動作を記述する
実装すると、以下のようになります。
struct ContentView: View {
@State var tableItems = [Item(title: "Item1", value: 9), Item(title: "Item2", value: 12), Item(title: "Item3", value: 15)]
var body: some View {
VStack {
Table(of: Item.self, columns: {
TableColumn("Title", content: { item in
Text(item.title)
})
TableColumn("Value", content: { item in
Text("\(item.value)")
})
}, rows: {
ForEach(tableItems) { item in
TableRow(item)
.draggable(item)
}
// List で使用している ViewModifier とは action の引数順が異なります
.dropDestination(for: Item.self, action: { dest, items in
var indexSet = IndexSet()
items.forEach({ item in
if let index = tableItems.firstIndex(of: item) {
indexSet.insert(index)
}
})
tableItems.move(fromOffsets: indexSet, toOffset: dest)
})
})
.animation(.bouncy, value: tableItems)
}
.padding()
}
}
以下のような動作になります。
animation 指定しているのですが、アニメーションされません・・・
理由をご存知の方、教えてください。
この記事では 行の並び替えのための Drag&Drop を例にしていますが、TableRow に対して、dropDestination を指定すると行に対して Drop できるようになります。
まとめ
Table での Drag&Drop の方法
- Drag&Drop する UTType を定義する
- draggable で Drag できるようにする
- dropDestination で受け取り処理する
- Table では、onMove は使えない
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
SwiftUI おすすめ本
SwiftUI を理解するには、以下の本がおすすめです。
SwiftUI ViewMatery
SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。
英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。
View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。
超便利です
販売元のページは、こちらです。
SwiftUI 徹底入門
# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。
Swift学習におすすめの本
詳解Swift
Swift の学習には、詳解 Swift という書籍が、おすすめです。
著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。
最新版を購入するのがおすすめです。
現時点では、上記の Swift 5 に対応した第5版が最新版です。
Sponsor Link