MENU

【SwiftUI入門】onMoveの使い方|リストの並び替え機能を実装する方法

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の動作の流れ

実際にアプリを動かすと、次のような流れで並び替えができます。

  1. 画面右上の「Edit」ボタンをタップ
  2. 各行の右側にドラッグハンドル(三本線のアイコン)が表示される
  3. ハンドルをドラッグして項目を移動
  4. 指を離すと新しい位置に項目が配置される
  5. 「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: 並び替えができない

原因: onMoveListではなく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の併用

多くのアプリでは、並び替えと削除の両方の機能が必要です。onMoveonDeleteは同時に使えます。

ForEach(items, id: \.self) { item in
    Text(item)
}
.onMove(perform: moveItem)
.onDelete(perform: deleteItem)

編集モード中は、左側に削除ボタン(赤い丸)、右側にドラッグハンドルが表示されます。

まとめ

この記事では、SwiftUIのonMoveを使ってリストの並び替え機能を実装する方法を解説しました。

重要なポイント:

  • onMoveForEachに適用する
  • データは@Stateで管理する
  • EditButtonで編集モードを切り替える
  • move(fromOffsets:toOffset:)でデータを更新する

onMoveを使えば、わずか数行のコードでユーザーフレンドリーな並び替え機能を実装できます。ToDoリストアプリやお気に入り管理アプリなど、さまざまな場面で活用してみてください。

プログラミングの独学におすすめ
プログラミング言語の人気オンラインコース
独学でプログラミングを学習している方で、エラーなどが発生して効率よく勉強ができないと悩む方は多いはず。Udemyは、プロの講師が動画で実際のプログラムを動かしながら教えてくれるオンライン講座です。講座の価格は、セール期間中には専門書籍を1冊買うよりも安く済むことが多いです。新しく学びたいプログラミング言語がある方は、ぜひUdemyでオンライン講座を探してみてください。
目次