In UIKit you can select multiple rows of a UITableView by using allowsMultipleSelection - can this be done with the List in SwiftUI?
-
1Yes. Check out how to use `EditButton`. https://www.hackingwithswift.com/quick-start/swiftui/how-to-enable-editing-on-a-list-using-editbutton – Jul 13 '19 at 20:38
-
2@dfd - Multiple selection and multi-row editing seem like different things. – Rob Jul 13 '19 at 20:42
-
@Rob, not sure what you mean. Technically, `allowsMultipleSelection` doesn't exist (yet) for a `List`. But functionally, if you use an `EditButton` with a List, you can select multiple rows, albeit *very* differently than a `UITableView`. – Jul 13 '19 at 20:46
-
3That’s exactly what I mean. They’re analogous, but completely different things. – Rob Jul 13 '19 at 20:49
-
Could I programmatically tap the EditButton so that when the view is presented multiple items can be selected? Use case is applying multiple tags to a File – Brandon Bradley Jul 13 '19 at 21:00
6 Answers
The only way to get multiple selection in SwiftUI right now is by using EditButton
. However, that's not the only instance you might want to use multiple selection, and it would probably confuse users if you used EditButton
multiple selection when you're not actually trying to edit anything.
I assume what you're really looking for is something like this:
Below is the code I wrote to create this:
struct MultipleSelectionList: View {
@State var items: [String] = ["Apples", "Oranges", "Bananas", "Pears", "Mangos", "Grapefruit"]
@State var selections: [String] = []
var body: some View {
List {
ForEach(self.items, id: \.self) { item in
MultipleSelectionRow(title: item, isSelected: self.selections.contains(item)) {
if self.selections.contains(item) {
self.selections.removeAll(where: { $0 == item })
}
else {
self.selections.append(item)
}
}
}
}
}
}
struct MultipleSelectionRow: View {
var title: String
var isSelected: Bool
var action: () -> Void
var body: some View {
Button(action: self.action) {
HStack {
Text(self.title)
if self.isSelected {
Spacer()
Image(systemName: "checkmark")
}
}
}
}
}

- 7,430
- 3
- 24
- 30
-
1
-
My full solution based on your example: https://www.pawelmadej.com/post/multi-select-picker-for-swiftui/ – Paweł Madej Nov 18 '19 at 19:18
-
@graycampbell Instead of using state I'm using "@Binding" and I get weird behaviour doing that sometimes. For instance clicking on a row will push (it's inside a NavigationView) a new view of the MultipleSelectionList... – Peter Warbo Jan 04 '20 at 17:38
-
@PeterWarbo It’s hard to say why that would be happening without seeing your code. If you want to post a new question and then link to it here, I’d be happy to take a look at it – graycampbell Jan 04 '20 at 19:25
-
Bless you. Best solution. With a Stateable List you can send back to another view if needed. Thanks! – DaWiseguy Jul 13 '20 at 17:41
First add this to your view
@State var selectedItems = Set<UUID>()
The type of the Set
depends on the type you use to id:
the items in the ForEach
Next declare the List
List(selection: $selectedItems) {
ForEach(items, id: \.id) { item in
Text("\(item.name)")
}
}
Now whatever you select gets added to the selectedItems Set
remember to clear it out after you use it.

- 644
- 1
- 7
- 12
I created a custom ToggleStyle
as follows:
import SwiftUI
enum Fruit: String, CaseIterable, Hashable {
case apple = "Apple"
case orange = "Orange"
case banana = "Banana"
}
struct ContentView: View {
@State var fruits = [Bool](repeating: false, count: Fruit.allCases.count)
var body: some View {
Form{
ForEach(0..<fruits.count, id:\.self){i in
Toggle(isOn: self.$fruits[i]){
Text(Fruit.allCases[i].rawValue)
}.toggleStyle(CheckmarkToggleStyle())
}
}
}
}
struct CheckmarkToggleStyle: ToggleStyle {
func makeBody(configuration: Self.Configuration) -> some View {
HStack {
Button(action: { withAnimation { configuration.$isOn.wrappedValue.toggle() }}){
HStack{
configuration.label.foregroundColor(.primary)
Spacer()
if configuration.isOn {
Image(systemName: "checkmark").foregroundColor(.primary)
}
}
}
}
}
}

- 7,672
- 3
- 36
- 48
I found an approach using a custom property wrapper that enables the selection to be modified from a child view using a Binding
:
struct Fruit: Selectable {
let name: String
var isSelected: Bool
var id: String { name }
}
struct FruitList: View {
@State var fruits = [
Fruit(name: "Apple", isSelected: true),
Fruit(name: "Banana", isSelected: false),
Fruit(name: "Kumquat", isSelected: true),
]
var body: some View {
VStack {
Text("Number selected: \(fruits.filter { $0.isSelected }.count)")
BindingList(items: $fruits) {
FruitRow(fruit: $0)
}
}
}
struct FruitRow: View {
@Binding var fruit: Fruit
var body: some View {
Button(action: { self.fruit.isSelected.toggle() }) {
HStack {
Text(fruit.isSelected ? "☑" : "☐")
Text(fruit.name)
}
}
}
}
}

- 8,438
- 7
- 41
- 48
Here is an alternate way that uses a helper I created called Multiselect
:
struct Fruit: Selectable {
let name: String
var isSelected: Bool
var id: String { name }
}
struct FruitList: View {
@State var fruits = [
Fruit(name: "Apple", isSelected: true),
Fruit(name: "Banana", isSelected: false),
Fruit(name: "Kumquat", isSelected: true),
]
var body: some View {
VStack {
Text("Number selected: \(fruits.filter { $0.isSelected }.count)")
Multiselect(items: $fruits) { fruit in
HStack {
Text(fruit.name)
Spacer()
if fruit.isSelected {
Image(systemName: "checkmark")
}
}
}
}
}
}
With the supporting code here:
protocol Selectable: Identifiable {
var name: String { get }
var isSelected: Bool { get set }
}
struct Multiselect<T: Selectable, V: View>: View {
@Binding var items: [T]
var rowBuilder: (T) -> V
var body: some View {
List(items) { item in
Button(action: { self.items.toggleSelected(item) }) {
self.rowBuilder(item)
}
}
}
}
extension Array where Element: Selectable {
mutating func toggleSelected(_ item: Element) {
if let index = firstIndex(where: { $0.id == item.id }) {
var mutable = item
mutable.isSelected.toggle()
self[index] = mutable
}
}
}

- 8,438
- 7
- 41
- 48
my 2 cents with a super simple solution:
import SwiftUI
struct ListDemo: View {
@State var items = ["Pizza", "Spaghetti", "Caviar"]
@State var selection = Set<String>()
var body: some View {
List(items, id: \.self, selection: $selection) { (item : String) in
let s = selection.contains(item) ? "√" : " "
HStack {
Text(s+item)
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
if selection.contains(item) {
selection.remove(item)
}
else{
selection.insert(item)
}
print(selection)
}
}
.listStyle(GroupedListStyle())
}
}
using string in set is sub-optimal, better to use id OR using strings with data and selection state.

- 10,876
- 3
- 61
- 48
-
Would this also be possible if you had multiple Sections? So your items would be something like `[["Pizza", "Spaghetti"], ["Caviar"]]`? – Georg Feb 09 '22 at 11:16
-
yes, but I prefer to use a viewModel or a more complex solution, a bit beyond this example. – ingconti Feb 10 '22 at 12:21