【SwiftUI】リストにスワイプアクションを追加して編集・削除

本記事ではリスト表示機能に、左右のスワイプをすることでリストからの削除や編集を行うことがきる機能を実現する方法について解説していきます。

なお、リスト表示の作成方法については以下の記事で解説していますのでこちらをご確認ください。

リスト表示の準備

最初にリストに表示する情報を保持する構造体の作成と1行分のビューを用意してそれらの情報をContentViewから表示します。

本記事では日付と曜日を表示したリストを用意していきます。

CalenderData

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: "火曜日")
    ]
}

DateInfoという名前で構造体を定義し、DateInfosというクラスをViewから監視可能な
ObservableObjectで作成します。

DateInfosクラスの中では、DateInfo構造体型のデータを格納する配列を定義し、構造体の各プロパティに実際の値を入れています。この際、IDの他にisCheckedというリストの既読/未読を扱う変数も構造体の初期値としてfalseが入っているため、改めて値を入れる必要はありません

ContentView

import SwiftUI

struct ContentView: View {
    @ObservedObject var dateInfos = DateInfos()
    var body: some View {
        List(dateInfos.dateinfodata){ item in
            DateView(item)
        }.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)
}

ContentView内では、Listを作成して先ほど定義したdateinfodataというリストをループ処理で回しています。ループ処理の中では、@ViewBuilderをつけたDateView関数に配列のデータを渡して、リスト1行分のViewをDateViewクラスの中で作成しています。

DateView関数内では既読/未読の状態によって表示を切り替えるようにしており、未読であれば黒、既読であれば文字色を灰色にするよう設定しています。

右スワイプで既読/未読の切り替え

スワイプ機能を追加するには、List内の各要素に対してswipeActionを設定し、スワイプした際に実行したい動作を作成する必要があります。

既読の切り替えの関数

toggleIsCheckedという関数を作成して構造体内の指定したインデックスのisCheckedの真偽値を切り替えることができる機能を作成します。

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()
    }
}

firstIndexを使用して、配列内の任意の要素が渡されてた際にそのインデックス番号を取得し、指定の要素に対して処理を行うことができます。

上記の処理では、isChecked.toggle()によってisCheckedプロパティの真偽値の切り替えを行なっています。

swipeActionの設定

リストをスワイプすることによって右や左にボタンを表示することができるのが今回紹介するスワイプ機能です。swipeAction()を各リストにつけることによって実現可能となります。

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)
                }
}.listStyle(.plain)

swipeActionのパラメータとして渡しているedgeには、左右どちらのスワイプにするかを指定し、leadingであれば左から右、trailingであれば右から左へのスワイプが可能となります。

swipeActionの中では、スワイプ実行後に表示したい要素を入れ、Buttonが一般的に配置される要素となります。Buttonクリック時の操作として、先ほど用意した既読/未読を切り替える関数を呼び出し、ボタンのラベルはisCheckedの値によって切り替える動きとなります。

実際に動かしてみると、左から右に任意のリストをスワイプすると「既読にする」というボタンが表示され、ボタンをクリックするとリストの文字列が灰色となり、もう一度スワイプすると「未読にする」が表示されることを確認できます。

初期表示

「既読にする」を実行後

左スワイプでリストの削除

先ほどとは反対に、右から左へのスワイプでリスト内から要素を削除する方法もご紹介していきます。

リストから要素を削除する関数

CalenderDateファイル内でリストから指定された要素を削除する関数を追加していきます。

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)
    }
}

配列から指定の要素を削除するのは、配列名.remove(at: インデックス番号)を使用することで実現可能です。呼び出し元から配列に存在するインデックス番号を指定することで、要素を削除することができます。

swipeActionの設定

ループ内の要素にもう一つのswipeActionを追加し、edgeにはtrailingを指定します。ButtonのクリックイベントではremoveDate関数を指定し、削除ボタンは赤色で表示したいため、role: .destructiveでボタンが赤色表示になるように設定します。

実際に動かすと、右から左へのスワイプで削除ボタンが表示され、削除ボタンをクリックすることで対象のリストが削除される動きを確認することができます。

初期状態

12/25のデータを削除実行後

最後に

本記事では、リスト表示に対してスワイプアクションを追加してリストの編集や削除を行う方法について解説してきました。

  • スワイプアクションの追加にはswipeAction()を使用する
  • swipeActionにはedgeを指定することで右スワイプ/左スワイプを決定できる
  • オブジェクトの要素を参照して、スワイプ時の表示を切り替えることができる

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