【SwiftUI】リストの行を入れ替え・削除ボタンを表示

本記事では、リスト表示の際にヘッダのツールバーに編集ボタンを追加して、リストの行をドラッグで入れ替えたり削除ボタンを表示する方法を解説していきます。

リストの作成

まずはリストの作成を行います。今回解説する機能とは異なりますが、以下の記事で紹介しているスワイプ機能を持ったリストを利用していきます。

詳しい解説は省略いたしますが、このリストは月日と曜日のデータを表示し、左にスワイプすることでリストの既読、右にスワイプすることで削除することができる機能を持ったリストです。

ContentView.swift

import SwiftUI

struct ContentView: View {
    @ObservedObject var dateInfos = DateInfos()
    var body: some View {
        List(dateInfos.dateinfodata){ item in
            DateView(item)
                .swipeActions(edge: .leading){
                    Button{
                        dateInfos.toggleIsChecked(item)
                    } label: {
                        if item.isChecked {
                            Label("未読にする", systemImage: "book.closed")
                        }else{
                            Label("既読にする", systemImage: "book.fill")
                        }
                    }.tint(.blue)
                }
                .swipeActions(edge: .trailing){
                    Button(role: .destructive){
                        dateInfos.removeDate(item)
                    } label: {
                        Label("削除", systemImage: "trash")
                    }
                }
        }.listStyle(.plain)
    }
}

//リスト1行分のViewを作成
@ViewBuilder
func DateView(_ item:DateInfo)-> some View{
    VStack(alignment: .leading){
        Text(item.date).bold()
        Text(item.day_of_week)
    }
    .foregroundColor(item.isChecked ? .gray : .black)
    .frame(height: 75)
}

#Preview {
    ContentView()
}

CalenderDate.swift

import Foundation

//構造体を定義
struct DateInfo: Identifiable, Equatable {
    var id = UUID()
    var date:String
    var day_of_week:String
    var isChecked = false
}

class DateInfos: ObservableObject{
    //構造体データを格納したリスト
    @Published var dateinfodata = [
        DateInfo(date: "12月22日", day_of_week: "日曜日"),
        DateInfo(date: "12月23日", day_of_week: "月曜日"),
        DateInfo(date: "12月24日", day_of_week: "火曜日"),
        DateInfo(date: "12月25日", day_of_week: "水曜日"),
        DateInfo(date: "12月26日", day_of_week: "木曜日"),
        DateInfo(date: "12月27日", day_of_week: "金曜日"),
        DateInfo(date: "12月28日", day_of_week: "土曜日"),
        DateInfo(date: "12月29日", day_of_week: "日曜日"),
        DateInfo(date: "12月30日", day_of_week: "月曜日"),
        DateInfo(date: "12月31日", day_of_week: "火曜日")
    ]
    
    //既読/未読の切り替え
    func toggleIsChecked(_ item: DateInfo){
        guard let index = dateinfodata.firstIndex(of: item) else{return}
        dateinfodata[index].isChecked.toggle()
    }
    
    //リストから要素の削除
    func removeDate(_ item: DateInfo){
        guard let index = dateinfodata.firstIndex(of: item) else{return}
        dateinfodata.remove(at: index)
    }
}

表示内容

編集ボタンの追加

上記のリストに対し、ヘッダ部分に編集ボタンを追加していきます。

まず編集ボタンをリストに追加する際には、NavigationViewを適用し、ListのループではなくList内でForEachにてループを行います。

NavigationView{
            List{
                ForEach(dateInfos.dateinfodata){ item in

NavigationViewとしたことによって画面上にヘッダエリアを追加することができましたので、ここにヘッダのタイトルとエディットボタンを追加していきます。

List{   
    ...
    ...             
}.listStyle(.plain)
                .navigationTitle(Text("カレンダーリスト"))
                .toolbar{
                    ToolbarItem(placement: .navigationBarTrailing){
                        EditButton()
                    }
                }

タイトルの表示には、navigationTitleを使用してtextでタイトルを指定します。Editボタンの追加はEditButton()をtoolbarの中にitemとして入れることで実現可能です。

EditButton()を実装することでヘッダに「Edit」が表示され、リストを編集モードに切り替えることができます。

入れ替え用ボタンの追加

リストがEditButton()によって編集モードになった際にリストを入れ替える挙動を実装していきます。

ForEach(dateInfos.dateinfodata){ item in
                    ...
                }
                .onMove{
                    IndexSet, index in
                    dateInfos.dateinfodata.move(fromOffsets: IndexSet, toOffset: index)
                }

ForEachに対して、onMoveモディファイアを指定することによってドラッグ&ドロップによってリストの行を移動して入れ替える機能を実装することができます。

配列内の実際にドラッグされた項目に、move()メソッドを使用することで行の移動を実行することができ、パラメータのfromOffsetsには対象項目の元々のインデックスを意味するIndexSetを、toOffsetには移動先のインデックスであるindexを指定します。

移動後の画面

onMoveで実際に行を移動させることができ、「12月23日」という行を「12月23日」の下に移動することが確認できました。

削除ボタンの追加

削除ボタンの実装は、onMoveモディファイアと同様にForEachに対してonDeleteモディファイアを追加していきます。

ForEach(dateInfos.dateinfodata){ item in
                    
                }
                .onMove{
                    IndexSet, index in
                    dateInfos.dateinfodata.move(fromOffsets: IndexSet, toOffset: index)
                }
                .onDelete{
                    IndexSet in
                    dateInfos.dateinfodata.remove(at:IndexSet.first!)
                }

onDeleteモディファイアを追加することで、editモードになった際に赤丸に白い横棒のアイコンが表示されます。アイコンをクリックすると削除ボタンが表示され、行を削除できます。

編集モード

赤いアイコンクリック

削除ボタンクリック

上記の一連の動作で無事に「12月24日」というデータの行が削除できたことが確認できました。

最後に

本記事では、リストにナビゲーションビューを追加してヘッダに編集ボタンを入れることで、リストの移動や削除を行うことができる機能について紹介してきました。

  • リストの編集ボタンを表示するには、NavigationViewを使用する
  • ListではなくForEachを使用してリストのループを行う
  • リストの入れ替えには、onMoveを使用する
  • リストの削除にはonDeleteを使用する

他にも@Binding変数を使用してリストから詳細画面への遷移やObservableObjectを使用したオブジェクトをViewと連携させる方法についても解説していますのでぜひご確認お願いします。