Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save SimplyKyra/5cf6ec5da50cb937854facc572a95b82 to your computer and use it in GitHub Desktop.
Save SimplyKyra/5cf6ec5da50cb937854facc572a95b82 to your computer and use it in GitHub Desktop.
Custom SwiftUI multipicker that allows custom list with image and multiple selection. Learn more about it here: https://www.simplykyra.com/update-to-my-custom-picker-with-multi-selection-in-swiftui-now-with-images/
//
// ContentView.swift
// Shared
//
// Created by Kyra on 2/2/22.
//
import SwiftUI
struct myItem: Hashable, Identifiable {
var id: Int
var name:String
var imageName:String
}
struct ContentView: View {
@State var selectedItems = [myItem]()
@State var allItems:[myItem] = [
myItem(id: 0, name: "smile", imageName: "face.smiling"),
myItem(id: 1, name: "heart", imageName: "heart"),
myItem(id: 2, name: "music", imageName: "music.quarternote.3"),
myItem(id: 3, name: "scribble", imageName: "scribble"),
myItem(id: 4, name: "fire", imageName: "flame"),
myItem(id: 5, name: "piano", imageName: "pianokeys"),
myItem(id: 6, name: "puzzle", imageName: "puzzlepiece"),
]
var body: some View {
#if os(macOS)
macOSview(selectedItems: selectedItems, allItems: allItems)
#else //iOS
iOSview(selectedItems: selectedItems, allItems: allItems)
#endif
}
}
// This View is functional on macOS and iOS; however, I prefer
// using a NavigationalLink for iOS rather than the popover so
// setting this one to macOS for this example
struct macOSview: View {
@State var selectedItems:[myItem]
@State var allItems:[myItem]
// Needed to show the popover
@State var showingPopover:Bool = false
var body: some View {
Form {
HStack() {
// Rather than a picker we're using Text for the label
// and a button for the picker itself
Text("Select Items:")
.foregroundColor(.white)
Button(action: {
// The only job of the button is to toggle the showing
// popover boolean so it pops up and we can select our items
showingPopover.toggle()
}) {
HStack {
Spacer()
Image(systemName: "\($selectedItems.count).circle")
.font(.title2)
Image(systemName: "chevron.right")
.font(.caption)
}
.foregroundColor(Color(red: 0.4192, green: 0.2358, blue: 0.3450))
}
.popover(isPresented: $showingPopover) {
MultiSelectPickerView(allItems: allItems, selectedItems: $selectedItems)
// If you have issues with it being too skinny you can hardcode the width
.frame(width: 300)
}
}
// Made a quick text section so we can see what we selected
Text("My selected items are:")
.foregroundColor(.white)
if selectedItems.count > 0 {
ForEach(selectedItems) { item in
HStack {
Text("\t*\(item.name) - ")
Image(systemName: item.imageName)
}
}
// Text("\t* \(selectedItems.joined(separator: "\n\t* "))")
.foregroundColor(.white)
}
}
.padding()
.background(Color(red: 0.4192, green: 0.2358, blue: 0.3450))
.navigationTitle("My Items")
}
}
// The iOS version uses a NavigationLink that requires being
// in a NavigationView to work so I chose to move the entire
// thing into a new View.
struct iOSview:View {
@State var selectedItems:[myItem]
@State var allItems:[myItem]
var body: some View {
NavigationView {
Form {
// Since this is for iOS I included sections
Section("Choose your items:", content: {
// Rather than a button we're using a NavigationLink but passing
// in the same destination
NavigationLink(destination: {
MultiSelectPickerView(allItems: allItems, selectedItems: $selectedItems)
.navigationTitle("Choose Your Items")
}, label: {
// And then the label and dynamic number are displayed in the label.
// We don't need to include the chevron as it's done for us in the link
HStack {
Text("Select Items:")
.foregroundColor(Color(red: 0.4192, green: 0.2358, blue: 0.3450))
Spacer()
Image(systemName: "\($selectedItems.count).circle")
.foregroundColor(Color(red: 0.4192, green: 0.2358, blue: 0.3450))
.font(.title2)
}
})
})
// Made a quick text section so we can see what we selected
Section("My selected items are:", content: {
ForEach(selectedItems) { item in
HStack {
Text("\t* \(item.name)")
Spacer()
Image(systemName: item.imageName)
Spacer()
}
.foregroundColor(Color(red: 0.4192, green: 0.2358, blue: 0.3450))
}
})
}
.navigationTitle("My Items")
}
}
}
// The struct that the custom picker (button) opens which
// is minorly adapted from:
// https://gist.github.com/dippnerd/5841898c2cf945994ba85871446329fa
struct MultiSelectPickerView: View {
// The list of items we want to show
@State var allItems: [myItem]
// Binding to the selected items we want to track
@Binding var selectedItems: [myItem]
var body: some View {
Form {
List {
ForEach(allItems, id: \.self) { item in
Button(action: {
withAnimation {
if self.selectedItems.contains(item) {
// Previous comment: you may need to adapt this piece
self.selectedItems.removeAll(where: { $0 == item })
} else {
self.selectedItems.append(item)
}
}
}) {
HStack {
Image(systemName: "checkmark")
.opacity(self.selectedItems.contains(item) ? 1.0 : 0.0)
Image(systemName: item.imageName)
}
}
.foregroundColor(.primary)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment