はじめに
Listの行を削除する方法についてまとめました。
開発環境
- macOS Big Sur 11.2.3
- Xcode 12.4
- Swift 5.3.2
実装
スワイプで行を削除したい場合
※参考: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() } }
選択して行を削除したい場合
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() } }
複数の行を同時に削除したい場合
以下のページを参考に、UIをアレンジして実装。
※参考:
- https://developer.apple.com/documentation/swiftui/list
-
How to delete multiple rows from List in SwiftUI? - Stack Overflow
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() } }
おわりに
SafariやミュージックといったApple純正の一部のアプリでは、「複数の行を同時に削除したい場合」で実装したような形式ではなく、「選択して行を削除したい場合」で実装したような形式で削除することに気づきました。
削除機能の使用頻度が比較的多くなるアプリの場合は、選択して一括削除する機能があったほうが使い勝手がよいと思います。しかし、一括削除のデメリットとして、ユーザーが誤って複数の項目を削除してしまう可能性もあります。そのため、削除頻度が少ないと想定されるアプリや、まとめて項目を削除する必要のないアプリに関しては「選択して行を削除したい場合」の形式で実装することで、あえて手軽に複数の項目を削除できないようにしておいた方がよいのかなと思いました。
参考
-
https://developer.apple.com/documentation/swiftui/foreach/ondelete(perform:)
-
How to let users delete rows from a list - a free SwiftUI by Example tutorial
-
https://developer.apple.com/documentation/swiftui/navigationview
-
https://developer.apple.com/documentation/swiftui/editbutton
-
How to delete multiple rows from List in SwiftUI? - Stack Overflow