
SwiftUIでリストの要素を並べ替える機能を実装したいときに便利なのが .onMove() です。
ドラッグ&ドロップ操作でユーザーが要素の順序を自由に変更できるようになるため、チェックリストやタスク管理アプリなど多くの場面で活用できます。
この記事では、.onMove() の基本的な使い方、引数の意味、活用シーンをわかりやすく解説します。
.onMove() とは?
.onMove() は、SwiftUI の List 内で並べ替え可能な状態を実現するためのモディファイアです。
具体的には、ユーザーがリスト上の要素をドラッグして移動させたときに、データの順序を更新する処理を受け取ることができます。
UIKitであれば UITableView に対して編集モードや delegate メソッドを設定する必要がありましたが、SwiftUIでは .onMove() を使うだけで非常に簡単に導入できます。
基本的な使い方
まず基本的な使い方を確認していきましょう。
1. シンプルな文字列配列の並べ替え
以下は、文字列の配列を並べ替え可能なリストとして表示する例です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import SwiftUI struct ContentView: View { @State private var items = ["りんご", "バナナ", "オレンジ", "ぶどう", "いちご"] var body: some View { NavigationStack { List { ForEach(items, id: \.self) { item in Text(item) } .onMove(perform: moveItems) } .navigationTitle("フルーツ") .toolbar { EditButton() } } } private func moveItems(from source: IndexSet, to destination: Int) { items.move(fromOffsets: source, toOffset: destination) } } |
このコードでは、ユーザーが対象のアイテムをドラッグ&ドロップで移動させることができます。
下記はシミュレーターの例なのでポインターで動かしていますが、実際は指でドラッグ&ドロップして並び替えられます。

ユーザーがドラッグするとonMove(perform:) に渡した move 関数が呼ばれ、配列の要素が新しい順番に更新されて並び替えが行われます。
EditButtonを使わない並べ替え
iOS 15以降では、特定の条件下で EditButton を使って編集モードにしなくても、.onMove() だけでドラッグ操作が有効になりました。
|
1 2 3 4 5 6 7 8 |
List { ForEach(items, id: \.self) { item in Text(item) } .onMove(perform: moveItems) .moveDisabled(false) // 確実にドラッグを有効化 } |
ただし、使用する場合は以下の点に注意が必要です:
- 条件: List内にSectionがない、シンプルな構造の場合に有効
- 推奨: 確実な動作のためには
EditButtonの使用を推奨 - 明示的指定:
.moveDisabled(false)で明示的にドラッグを有効化(指定しなくても並び替えできるが複雑な構造は明示的に指定を推奨)
.onMove(perform:) の仕組みと引数の意味
.onMove(perform:) は 「ユーザーがリストをドラッグで並び替えたときに呼び出される処理」 を受け取ります。
ここで.onMove(perform:)が受け取るのは「並び替えのロジックを記述した関数」や「無名クロージャ」です。
クロージャのシグネチャ
|
1 2 |
func moveItems(from source: IndexSet, to destination: Int) |
.onMove(perform:) は、この moveItems 関数(関数名はmoveなど別名でもOK)を 引数として受け取る ことができます。
言い換えると、moveItems は「ユーザー操作に応じて呼ばれる処理の中身」を担当し、その参照を .onMove に渡している形になります。
SwiftUI 側でドラッグ操作が発生すると、内部で moveItems(from:to:) が呼び出され、データを並び替える処理が実行されます。
役割を整理すると下記となります。
.onMove= 「ユーザーが並び替えたときに呼ぶよ、と登録する場所」moveItems= 「並び替えが起きたときに実際にどう処理するかを書く関数」
つまり、.onMove(perform: moveItems) と書くことで、イベントハンドラとして moveItems を渡している、という構造です。
並び替えが起きた時に処理する関数は下記2つの引数を受け取ります。
| 引数名 | 型 | 説明 |
|---|---|---|
source |
IndexSet |
移動対象となる要素のインデックス(複数選択可能) |
destination |
Int |
移動先のインデックス |
私がこの引数を見た時に下記のような疑問が出たので、そちらも備忘録として記載しておきます。
- IndexSet って何?
複数の整数インデックスをまとめて管理するためのコレクション型です。連続した範囲やバラバラのインデックスを一度に保持できます。 -
なぜ
sourceはIndexSetなのか?
リスト上で複数の要素を同時に移動できるようにするためです。ユーザーが一度に複数のアイテムを選択してドラッグした場合にも対応できます。 -
なぜ
destinationはIntなのか?
移動先は「最終的に挿入される位置」を示す単一の場所だけを指定すれば十分だからです。
複数の要素を動かす場合でも、そのまとまりが新しく配置される位置はひとつのインデックスで表現できます。
注意点とベストプラクティス
.onMove(perform:) を使う際の注意点とベストプラクティを簡単にまとめます。
必須要件
ForEachはListの直下にある必要がある- データには一意の
idが必要(.selfやUUIDなど) - 配列のデータは
@Stateなどで状態管理する必要がある
推奨事項
- 確実な動作のため
EditButton()を使用する - 複雑なレイアウトでは
.moveDisabled(false)を明示する - アニメーションを追加してユーザビリティを向上させる
- 大量データの場合はパフォーマンスを考慮する
避けるべき書き方
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//ネストが深すぎる List { VStack { HStack { ForEach(...) { ... } .onMove(...) // 動かない可能性 } } } //データの状態管理なし let staticData = ["固定データ"] // @Stateでないため、並び替えが反応しない可能性 //IDが重複している可能性 ForEach(items, id: \.title) { ... } // titleが重複している場合、うまく並び替えられない可能性 |
まとめ
.onMove() は SwiftUI でユーザーによる並べ替え機能を簡単に実装できる強力なモディファイアです。
重要なポイント:
.onMove()は List 上で並べ替え可能な状態を作るmove(fromOffsets:toOffset:)を使って配列の順序を更新- 確実な動作には
EditButtonの使用を推奨 - 複雑なレイアウトでは
.moveDisabled(false)を明示 - データは必ず
@Stateなどで状態管理する
タスク管理アプリ、チェックリスト、カスタマイズ可能なメニューなど、ユーザーが順序を自由に変更できる機能を簡単に実装できます。
まずは基本的な使い方から始めて、段階的に複雑な機能を追加していくのがおすすめです!
