Last active
April 29, 2020 11:56
-
-
Save ccwasden/4a5247249866f63b87af15a58bea3c12 to your computer and use it in GitHub Desktop.
Swift multiselect
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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