Skip to content

Instantly share code, notes, and snippets.

@ccwasden
Last active April 29, 2020 11:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save ccwasden/4a5247249866f63b87af15a58bea3c12 to your computer and use it in GitHub Desktop.
Save ccwasden/4a5247249866f63b87af15a58bea3c12 to your computer and use it in GitHub Desktop.
Swift multiselect
//
// Author: Chase Wasden
// Website: https://gist.github.com/ccwasden
// Licensed under MIT license https://opensource.org/licenses/MIT
//
import SwiftUI
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),
]
// Alternative body (doesn't have full binding access):
//
// 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")
// }
// }
// }
// }
// }
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.name)
Spacer()
if fruit.isSelected {
Image(systemName: "checkmark")
}
}
}
}
}
}
// ---------------------- The Reusable Magic ---------------------- //
protocol Selectable: Identifiable {
var name: String { get }
var isSelected: Bool { get set }
}
struct Multiselect<T: Selectable, V: View>: View {
@Binding var items: [T]
let rowBuilder: (T) -> V
var body: some View {
BindingList(items: $items) { item in
Button(action: { item.wrappedValue.isSelected.toggle() }) {
self.rowBuilder(item.wrappedValue)
}
}
}
}
struct BindingList<T: Identifiable, V: View>: View {
@BindingArray var items: Binding<[T]>
let rowBuilder: ((Binding<T>) -> V)
var body: some View {
List($items, rowContent: rowBuilder)
}
}
extension Binding: Identifiable where Value: Identifiable {
public var id: Value.ID {
return wrappedValue.id
}
}
@propertyWrapper
struct BindingArray<Value> : DynamicProperty {
@Binding var storage: [Value]
init(wrappedValue value: Binding<[Value]>) {
self._storage = value
}
public var wrappedValue: Binding<[Value]> {
get { _storage }
nonmutating set { storage = newValue.wrappedValue }
}
public var projectedValue: [Binding<Value>] {
var bindings = [Binding<Value>]()
for (i, item) in storage.enumerated() {
bindings.append(Binding(
get: { item },
set: { self.wrappedValue[i].wrappedValue = $0 }
))
}
return bindings
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment