Swift・iOS

Swiftを中心に学んだことを記録に残すブログです。技術に関係ない記事もたまに書いています。

【SwiftUI】Listの行を削除する方法(スワイプで削除/選択して削除/複数の行を同時に削除)

 

はじめに

Listの行を削除する方法についてまとめました。

 

開発環境

 

実装

スワイプで行を削除したい場合

※参考:How to let users delete rows from a list - a free SwiftUI by Example tutorial

ContentView.swift

import SwiftUI

struct ContentView: View {
    @State private var regions = ["北海道", "東北", "関東", "中部", "近畿", "中国", "四国", "九州"]
    
    var body: some View {
        List {
            ForEach(regions, id: \.self) { region in
                Text(region)
            }
            .onDelete(perform: delete)
        }
        .listStyle(InsetGroupedListStyle())
    }
    
    func delete(offsets: IndexSet) {
        regions.remove(atOffsets: offsets)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

f:id:hfoasi8fje3:20210329170838g:plain

 

選択して行を削除したい場合

NavigationViewとEditButtonを追加することで実現できる。

参考:https://developer.apple.com/documentation/swiftui/editbutton

ContentView.swift

import SwiftUI

struct ContentView: View {
    @State private var regions = ["北海道", "東北", "関東", "中部", "近畿", "中国", "四国", "九州"]
    
    var body: some View {
        NavigationView {
            List {
                ForEach(regions, id: \.self) { region in
                    Text(region)
                }
                .onDelete { indexSet in
                    regions.remove(atOffsets: indexSet)
                }
            }
            .listStyle(InsetGroupedListStyle())
            .navigationTitle("地方")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar { EditButton() }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

f:id:hfoasi8fje3:20210329172122g:plain

 

複数の行を同時に削除したい場合

以下のページを参考に、UIをアレンジして実装。

※参考:

 

EditModeの状態を判別できるようにしておき、EditModeが.activeになった時(EditButtonが選択された時)に行を削除する処理を実行するテキストを表示する。

行の削除処理に関しては、変数regionsが持つそれぞれのアイテムのidと、選択したアイテムの情報を監視している変数multiSelectionが持つアイテムのidを照らし合わせて、合致するものがあれば削除する。

 

※なぜ行を削除するボタンではなく行を削除するテキスト?

結論はボタンで実装したほうがいいのですが、今回は以下の都合でテキストでの実装にしました。

  • 今回の場合、Listの外(上、もしくは下)にボタンを表示しようとすると、".listStyle(InsetGroupedListStyle())"を適用したListと".listStyle(InsetGroupedListStyle())"を適用していないボタンの背景色とUIに差異が出てしまうため。
  • Listの中にボタンとForEachを実装しても、ボタンの処理(行の削除処理)を実行できないため。

 

ContentView.swift

import SwiftUI

struct ContentView: View {
    struct Region: Identifiable {
        let id = UUID()
        let name: String
    }
    
    @State var regions = [
        Region(name: "北海道"),
        Region(name: "東北"),
        Region(name: "関東"),
        Region(name: "中部"),
        Region(name: "近畿"),
        Region(name: "中国"),
        Region(name: "四国"),
        Region(name: "九州")
    ]
    
    @State var editMode: EditMode = .inactive
    @State var multiSelection = Set<UUID>()
    
    var body: some View {
        NavigationView {
            VStack {
                List(selection: $multiSelection) {
                        if editMode == .active {
                            Text("選択した項目を削除")
                                .foregroundColor(Color.red)
                                .onTapGesture {
                                    deleteItems()
                                }
                        }
                    
                        ForEach(regions) { region in
                            Text(region.name)
                        }
                }
                .listStyle(InsetGroupedListStyle())
                .navigationTitle("地方")
                .navigationBarTitleDisplayMode(.inline)
                .toolbar{ EditButton() }
                .environment(\.editMode, $editMode)
            }
        }
    }
    
    func deleteItems() {
        for id in multiSelection {
            if let index = regions.lastIndex(where: { $0.id == id }) {
                regions.remove(at: index)
            }
        }
        multiSelection = Set<UUID>()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

f:id:hfoasi8fje3:20210329190734g:plain

 

おわりに

SafariやミュージックといったApple純正の一部のアプリでは、「複数の行を同時に削除したい場合」で実装したような形式ではなく、「選択して行を削除したい場合」で実装したような形式で削除することに気づきました。

削除機能の使用頻度が比較的多くなるアプリの場合は、選択して一括削除する機能があったほうが使い勝手がよいと思います。しかし、一括削除のデメリットとして、ユーザーが誤って複数の項目を削除してしまう可能性もあります。そのため、削除頻度が少ないと想定されるアプリや、まとめて項目を削除する必要のないアプリに関しては「選択して行を削除したい場合」の形式で実装することで、あえて手軽に複数の項目を削除できないようにしておいた方がよいのかなと思いました。

 

参考