SwiftUIでリストアプリを作っていると、「ユーザーが自由に項目を並び替えられるようにしたい」という場面に出会います。そんなときに活躍するのがonMoveモディファイアです。
この記事では、SwiftUI初心者の方でも理解できるように、onMoveの基本的な使い方から実践的なサンプルコードまで、わかりやすく解説します。
onMoveとは?
onMoveは、SwiftUIのリスト内で要素をドラッグ&ドロップで並び替える機能を実装するためのモディファイアです。
ToDoリストアプリやお気に入りリストなど、ユーザーが優先順位を変更できる機能を簡単に実装できます。
onMoveの特徴
- iOSの標準的な並び替えUIを自動で提供
- 数行のコードで実装可能
- 編集モードと組み合わせて使用
onMoveの基本的な使い方
最小限のサンプルコード
まずは、最もシンプルな実装例を見てみましょう。
import SwiftUI
struct ContentView: View {
@State private var fruits = ["りんご", "バナナ", "オレンジ", "ぶどう", "いちご"]
var body: some View {
NavigationView {
List {
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
.onMove(perform: moveFruit)
}
.navigationTitle("果物リスト")
.toolbar {
EditButton()
}
}
}
func moveFruit(from source: IndexSet, to destination: Int) {
fruits.move(fromOffsets: source, toOffset: destination)
}
}
コードの解説
このコードのポイントを1つずつ見ていきましょう。
1. @State変数でデータを管理
@State private var fruits = ["りんご", "バナナ", "オレンジ", "ぶどう", "いちご"]
並び替え可能なデータは@Stateで管理します。これにより、データが変更されるとビューが自動で更新されます。
2. ForEachでリストを生成
ForEach(fruits, id: \.self) { fruit in
Text(fruit)
}
ForEachを使って配列の各要素をリスト項目として表示します。id: \.selfは各要素を一意に識別するために必要です。
3. onMoveモディファイアを追加
.onMove(perform: moveFruit)
ForEachに対してonMoveを適用します。引数には並び替え処理を行う関数を指定します。
4. 並び替え処理の実装
func moveFruit(from source: IndexSet, to destination: Int) {
fruits.move(fromOffsets: source, toOffset: destination)
}
この関数が実際の並び替え処理を行います。
source: 移動する要素のインデックスdestination: 移動先の位置
5. EditButtonで編集モードを切り替え
.toolbar {
EditButton()
}
EditButtonをツールバーに配置することで、編集モードのオン・オフを切り替えられます。編集モード中のみ並び替えが可能になります。
onMoveの動作の流れ
実際にアプリを動かすと、次のような流れで並び替えができます。
- 画面右上の「Edit」ボタンをタップ
- 各行の右側にドラッグハンドル(三本線のアイコン)が表示される
- ハンドルをドラッグして項目を移動
- 指を離すと新しい位置に項目が配置される
- 「Done」ボタンで編集モードを終了
実践的なサンプル:ToDoリストアプリ
より実践的な例として、チェックボックス付きのToDoリストを作ってみましょう。
import SwiftUI
struct TodoItem: Identifiable {
let id = UUID()
var title: String
var isCompleted: Bool
}
struct TodoListView: View {
@State private var todos = [
TodoItem(title: "買い物に行く", isCompleted: false),
TodoItem(title: "メールを返信する", isCompleted: true),
TodoItem(title: "資料を作成する", isCompleted: false),
TodoItem(title: "ミーティングの準備", isCompleted: false)
]
var body: some View {
NavigationView {
List {
ForEach($todos) { $todo in
HStack {
Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundColor(todo.isCompleted ? .green : .gray)
.onTapGesture {
todo.isCompleted.toggle()
}
Text(todo.title)
.strikethrough(todo.isCompleted)
.foregroundColor(todo.isCompleted ? .gray : .primary)
}
}
.onMove(perform: moveTodo)
.onDelete(perform: deleteTodo)
}
.navigationTitle("やることリスト")
.toolbar {
EditButton()
}
}
}
func moveTodo(from source: IndexSet, to destination: Int) {
todos.move(fromOffsets: source, toOffset: destination)
}
func deleteTodo(at offsets: IndexSet) {
todos.remove(atOffsets: offsets)
}
}
このサンプルでは、並び替え機能(onMove)に加えて、削除機能(onDelete)も実装しています。より実用的なToDoリストアプリの基本形となっています。
よくあるエラーと対処法
エラー1: ドラッグハンドルが表示されない
原因: EditButtonを配置していない、または編集モードになっていない
解決方法: ツールバーにEditButton()を追加し、ボタンをタップして編集モードに入る
エラー2: 並び替えができない
原因: onMoveをListではなくForEachに適用していない
解決方法: onMoveは必ずForEachに対して適用する
// ❌ 間違い
List {
ForEach(items, id: \.self) { item in
Text(item)
}
}
.onMove(perform: moveItem)
// ✅ 正しい
List {
ForEach(items, id: \.self) { item in
Text(item)
}
.onMove(perform: moveItem)
}
エラー3: 並び替え後にデータが元に戻る
原因: @Stateを使っていない、またはmoveメソッドを正しく呼び出していない
解決方法: 配列を@Stateで宣言し、move(fromOffsets:toOffset:)を使う
onMoveとonDeleteの併用
多くのアプリでは、並び替えと削除の両方の機能が必要です。onMoveとonDeleteは同時に使えます。
ForEach(items, id: \.self) { item in
Text(item)
}
.onMove(perform: moveItem)
.onDelete(perform: deleteItem)
編集モード中は、左側に削除ボタン(赤い丸)、右側にドラッグハンドルが表示されます。
まとめ
この記事では、SwiftUIのonMoveを使ってリストの並び替え機能を実装する方法を解説しました。
重要なポイント:
onMoveはForEachに適用する- データは
@Stateで管理する EditButtonで編集モードを切り替えるmove(fromOffsets:toOffset:)でデータを更新する
onMoveを使えば、わずか数行のコードでユーザーフレンドリーな並び替え機能を実装できます。ToDoリストアプリやお気に入り管理アプリなど、さまざまな場面で活用してみてください。