Last active
March 18, 2021 20:15
-
-
Save mattyoung/e6f9bf8391f16f06b1929d8d7fac2211 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| import SwiftUI | |
| protocol Selectable: Identifiable { | |
| associatedtype Label : View | |
| var isSelected: Bool { get set } | |
| var label: Label { get } | |
| } | |
| struct Multiselect<T: Selectable>: View { | |
| @Binding var items: [T] | |
| var body: some View { | |
| List { | |
| ForEach($items) { index, item in | |
| Toggle(isOn: item.isSelected) { | |
| item.wrappedValue.label | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // MARK: custom toggle styles | |
| // ========================== | |
| struct CheckboxToggleStyle: ToggleStyle { | |
| @Environment(\.isEnabled) var isEnabled | |
| func makeBody(configuration: Configuration) -> some View { | |
| Button(action: { configuration.isOn.toggle() }){ | |
| HStack { | |
| configuration.label | |
| .frame(maxWidth: .infinity, alignment: .leading) | |
| Image(systemName: configuration.isOn ? "checkmark.square" : "square") | |
| .resizable() | |
| .frame(width: 18, height: 18) | |
| } | |
| } | |
| .foregroundColor(isEnabled ? .accentColor : .gray) | |
| } | |
| } | |
| struct CheckmarkToggleStyle: ToggleStyle { | |
| @Environment(\.isEnabled) var isEnabled | |
| func makeBody(configuration: Configuration) -> some View { | |
| Button(action: { withAnimation { configuration.isOn.toggle() } }){ | |
| HStack { | |
| configuration.label | |
| .foregroundColor(.primary) | |
| .frame(maxWidth: .infinity, alignment: .leading) | |
| if configuration.isOn { | |
| Image(systemName: "checkmark") | |
| .foregroundColor(.accentColor) | |
| .animation(.default) | |
| .transition(.move(edge: .trailing).combined(with: .opacity)) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // MARK: demo example | |
| // ================== | |
| struct StringSelectable: Selectable { | |
| let name: String | |
| var isSelected = false | |
| var label: some View { Text(name) } | |
| var id: String { name } | |
| } | |
| struct MultiselectDemo: View { | |
| @State var fruits = [ | |
| StringSelectable(name: "Apple", isSelected: true), | |
| StringSelectable(name: "Banana"), | |
| StringSelectable(name: "Kumquat", isSelected: true), | |
| ] | |
| var body: some View { | |
| Form { | |
| Text("Number selected: \(fruits.filter { $0.isSelected }.count)") | |
| Section(header: Text("Juicy Fruits")) { | |
| Multiselect(items: $fruits) | |
| .toggleStyle(CheckmarkToggleStyle()) | |
| } | |
| Section(header: Text("Sour")) { | |
| Multiselect(items: $fruits) | |
| } | |
| Section(header: Text("Bitter")) { | |
| Multiselect(items: $fruits) | |
| .toggleStyle(CheckboxToggleStyle()) | |
| } | |
| } | |
| } | |
| } | |
| struct MultiselectDemo_Previews: PreviewProvider { | |
| static var previews: some View { | |
| MultiselectDemo() | |
| } | |
| } | |
| // see: https://swiftbysundell.com/articles/bindable-swiftui-list-elements/ | |
| struct IdentifiableIndices<Base> where Base: RandomAccessCollection, Base.Element: Identifiable { | |
| typealias Index = Base.Index | |
| struct Element: Identifiable { | |
| let id: Base.Element.ID | |
| let rawValue: Index | |
| func callAsFunction() -> Index { rawValue } | |
| } | |
| fileprivate var base: Base | |
| } | |
| extension IdentifiableIndices: RandomAccessCollection { | |
| var startIndex: Index { base.startIndex } | |
| var endIndex: Index { base.endIndex } | |
| subscript(position: Index) -> Element { | |
| Element(id: base[position].id, rawValue: position) | |
| } | |
| func index(before index: Index) -> Index { | |
| base.index(before: index) | |
| } | |
| func index(after index: Index) -> Index { | |
| base.index(after: index) | |
| } | |
| } | |
| extension ForEach where ID == Data.Element.ID, Data.Element: Identifiable, Content: View { | |
| init<T>(_ data: Binding<T>, @ViewBuilder content: @escaping (T.Index, Binding<T.Element>) -> Content) where Data == IdentifiableIndices<T>, T: MutableCollection { | |
| self.init(IdentifiableIndices(base: data.wrappedValue)) { index in | |
| content( | |
| index(), | |
| Binding( | |
| get: { data.wrappedValue[index()] }, | |
| set: { data.wrappedValue[index()] = $0 } | |
| ) | |
| ) | |
| } | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment