Posts

Multi Select Picker for SwiftUI

I was looking for ability to select multiple items. During my exploration I have found that there is no possiblity to make it select more than one item. This fact was also confirmed by Paul Hudson on his #Slack channel. Being in this situation I searched for any examples of such code.

This led me to Stackoverflow where I have found partially working solution. So after many refinements and tweaks I managed to simulate Picker and be able to select multiple items.

Below is complete working code with example Language Enum as source of choices. When you select “Choose languages” selection sheet is presented. When you select 0 or more item and hit “OK” button modal is dismissed and counter on “Choose languages” row is updated to number of items choosen.

This code is very rough, and there are many places where it could be written better, but this is its first implementation which works as I wanted.

If you have any comments about below elements feel free to comment on Twitter.

This is main view which will present Picker:

ContentView

import Combine
import SwiftUI

struct ContentView: View {
    @State private var showLanguageSheet = false

    @State private var x = 0

    @ObservedObject var preferedLanguages = PreferedLanguages()

    var body: some View {
        NavigationView {
            VStack {
                Form {
                    Section(header: Text("Language").font(.caption)) {
                        Button(action: {
                            self.showLanguageSheet.toggle()
                        }) {
                            HStack {
                                Text("Choose languages").foregroundColor(Color.black)
                                Spacer()
                                Text("\(preferedLanguages.languages.count)")
                                    .foregroundColor(Color(UIColor.systemGray))
                                    .font(.body)
                                Image(systemName: "chevron.right")
                                    .foregroundColor(Color(UIColor.systemGray4))
                                    .font(Font.body.weight(.medium))

                            }
                        }
                        .sheet(isPresented: $showLanguageSheet) {
                            SettingsLanguagePickerView(self.preferedLanguages)
                        }

                        Picker(selection: $x, label: Text("One item Picker")) {
                           ForEach(0..<10) { x in
                              Text("\(x)")
                           }
                        }

                        NavigationLink(destination: self) {
                            Text("Default navigation link")
                        }
                    }
                }
            }
            .navigationBarTitle("Content")
        }
    }
}

Source of Picker items - in this example Language enum

Language Enum

import Combine
import SwiftUI

enum Language: Int, CaseIterable, Identifiable {
    case english = 0
    case polish

    var id: Language {
        self
    }

    var literal: String {
        switch self {
        case .english: return "English"
        case .polish: return "Polish"
        }
    }
}

Combine helper which passes updated values between views and updates picker with previously choosen values

Combine Helper class to expose selected language array

class PreferedLanguages: ObservableObject {
    @Published var languages = [Language]()

}

Main Picker View

Multiple Select Picker

struct SettingsLanguagePickerView: View {
    @State private var selections = [Language]()

    @ObservedObject var preferedLanguages: PreferedLanguages

    init(_ preferedLanguages: PreferedLanguages) {
        self.preferedLanguages = preferedLanguages
    }

    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        NavigationView {
            List {
                Section(header: Text("Choose prefered languages")) {
                    ForEach(Language.allCases) { item in
                        MultipleSelectionRow(title: item.literal, isSelected: self.selections.contains(item)) {
                            if self.selections.contains(item) {
                                self.selections.removeAll(where: { $0 == item })
                            }
                            else {
                                self.selections.append(item)
                            }
                        }
                    }

                }
            }
            .onAppear(perform: { self.selections = self.preferedLanguages.languages })
            .listStyle(GroupedListStyle())
            .navigationBarTitle("Languages", displayMode: .inline)
            .navigationBarItems(trailing:
                Button(action: {
                    self.preferedLanguages.languages = self.selections
                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("OK")
                }
            )
        }
    }
}

Picker Row

Multiple select row

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").foregroundColor(.blue)
                }
            }
        }.foregroundColor(Color.black)
    }
}

Credits for base implementation of Multiple Select Picker goes to graycampbell and his implementation which I have found at StackOverflow: https://stackoverflow.com/a/57023746/1285959.

This article was mentioned in:

#SwiftUI, #Picker