[SwiftUI] Table で Row を Drag&Drop する

SwiftUI2021

     
⌛️ 2 min.

SwiftUI の Table で Row を Drag&Drop によって並び替える方法を確認します。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS14.6
  • Xcode 16.1 Beta
  • iOS 17.5
  • Swift 5.9

Table

要素を表形式で表示する時に使用する View です。

以前の記事で説明しています。

SwiftUI2021 [SwiftUI] Table の使い方

SwiftUI2021 [SwiftUI][macOS] Table の使い方

SwiftUI2021 [SwiftUI][macOS] Table の使い方(2)

Drag&Drop による並び替え

表の一部を Drag&Drop で入れ替えるような UI は、以前からよく使用されています。

MEMO

以下の例はすべて、同一リスト内から 行を入れ替えるための 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 でも使用されます。

以前、以下の記事で説明しました。

SwiftUI2021 [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 については、以下の記事の中で解説しています。
SwiftUI [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()
    }
}

以下のような動作になります。

MEMO

animation 指定しているのですが、アニメーションされません・・・
理由をご存知の方、教えてください。

MEMO

この記事では 行の並び替えのための Drag&Drop を例にしていますが、TableRow に対して、dropDestination を指定すると行に対して Drop できるようになります。

まとめ

Table での Drag&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 もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。

超便利です

SwiftUIViewsMastery

販売元のページは、こちらです。

SwiftUI 徹底入門

# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。

Swift学習におすすめの本

詳解Swift

Swift の学習には、詳解 Swift という書籍が、おすすめです。

著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。

最新版を購入するのがおすすめです。

現時点では、上記の Swift 5 に対応した第5版が最新版です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です