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リストアプリやお気に入り管理アプリなど、さまざまな場面で活用してみてください。