Skip to content

Commit bef909b

Browse files
author
Reed Es
committed
Improved picker; added multi-picker; API simplified
1 parent 837a9e8 commit bef909b

File tree

5 files changed

+178
-44
lines changed

5 files changed

+178
-44
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ DerivedData/
77
.swiftpm/config/registries.json
88
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
99
.netrc
10+
Package.resolved

.swift-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
5.7.2

Sources/PresetsPickerMulti.swift

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//
2+
// PresetsPickerMulti.swift
3+
//
4+
// Copyright 2023 OpenAlloc LLC
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
19+
import Collections
20+
import os
21+
import SwiftUI
22+
23+
// TODO: replace with @available
24+
#if !os(watchOS)
25+
26+
public struct PresetsPickerMulti<Key, PresettedItem, Label>: View
27+
where Key: Hashable & CustomStringConvertible,
28+
PresettedItem: PresettableItem,
29+
Label: View
30+
{
31+
public typealias PresetsDictionary = OrderedDictionary<Key, [PresettedItem]>
32+
33+
// MARK: - Parameters
34+
35+
@Binding private var selected: Set<PresettedItem>
36+
private let presets: PresetsDictionary
37+
private let label: (PresettedItem) -> Label
38+
39+
public init(selected: Binding<Set<PresettedItem>>,
40+
presets: PresetsDictionary,
41+
label: @escaping (PresettedItem) -> Label)
42+
{
43+
_selected = selected
44+
self.presets = presets
45+
self.label = label
46+
}
47+
48+
// MARK: - Views
49+
50+
public var body: some View {
51+
List(selection: $selected) {
52+
ForEach(presets.elements, id: \.key) { element in
53+
Section(header: Text(element.key.description)) {
54+
ForEach(element.value, id: \.self) { item in
55+
label(item)
56+
}
57+
}
58+
}
59+
}
60+
#if os(iOS)
61+
// force edit mode
62+
.environment(\.editMode, .constant(.active))
63+
#endif
64+
.toolbar {
65+
Button(action: selectAllAction) { Text("Select All") }
66+
Button(action: clearAction) { Text("Clear All") }
67+
}
68+
}
69+
70+
private func selectAllAction() {
71+
presets.forEach { $0.value.forEach { selected.insert($0) } }
72+
}
73+
74+
private func clearAction() {
75+
selected.removeAll()
76+
}
77+
}
78+
79+
struct PresetsPickerMulti_Previews: PreviewProvider {
80+
struct TestHolder: View {
81+
let presets: OrderedDictionary = [
82+
"Machine/Free Weights": [
83+
"Abdominal",
84+
"Arm Curl",
85+
"Arm Ext",
86+
],
87+
"Bodyweight": [
88+
"Crunch",
89+
"Jumping-jack",
90+
"Jump",
91+
],
92+
]
93+
94+
@State var showPresets = false
95+
@State var selected: Set<String> = .init()
96+
var body: some View {
97+
NavigationStack {
98+
PresetsPickerMulti(selected: $selected, presets: presets) {
99+
Text($0.text)
100+
}
101+
}
102+
}
103+
}
104+
105+
static var previews: some View {
106+
TestHolder()
107+
}
108+
}
109+
110+
#endif
Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// PresetsPicker.swift
2+
// PresetsPickerSingle.swift
33
//
44
// Copyright 2023 OpenAlloc LLC
55
//
@@ -20,58 +20,73 @@ import Collections
2020
import os
2121
import SwiftUI
2222

23-
struct PresetsPicker<Key, PresettedItem, Label>: View
23+
public struct PresetsPickerSingle<Key, PresettedItem, Label>: View
2424
where Key: Hashable & CustomStringConvertible,
2525
PresettedItem: PresettableItem,
2626
Label: View
2727
{
28-
typealias PresetsDictionary = OrderedDictionary<Key, [PresettedItem]>
29-
typealias OnSelect = (Key, PresettedItem) -> Void
28+
public typealias PresetsDictionary = OrderedDictionary<Key, [PresettedItem]>
3029

3130
// MARK: - Parameters
3231

3332
private let presets: PresetsDictionary
34-
@Binding private var showPresets: Bool
35-
private let onSelect: OnSelect
33+
private let onSelect: (PresettedItem) -> Void
3634
private let label: (PresettedItem) -> Label
3735

38-
init(presets: PresetsPicker.PresetsDictionary,
39-
showPresets: Binding<Bool>,
40-
onSelect: @escaping OnSelect,
41-
@ViewBuilder label: @escaping (PresettedItem) -> Label)
36+
public init(presets: PresetsDictionary,
37+
onSelect: @escaping (PresettedItem) -> Void,
38+
label: @escaping (PresettedItem) -> Label)
4239
{
43-
self.presets = presets
44-
_showPresets = showPresets
4540
self.onSelect = onSelect
41+
self.presets = presets
4642
self.label = label
4743
}
4844

45+
// MARK: - Locals
46+
47+
@State private var selected: PresettedItem?
48+
4949
// MARK: - Views
5050

51-
var body: some View {
52-
List {
53-
ForEach(presets.keys, id: \.self) { key in // .sorted(by: <)
54-
Section(header: Text(key.description)) {
55-
ForEach(presets[key]!, id: \.self) { value in // .sorted(by: <)
56-
Button {
57-
onSelect(key, value)
58-
showPresets = false
59-
} label: {
60-
label(value)
61-
}
51+
public var body: some View {
52+
platformView {
53+
ForEach(presets.elements, id: \.key) { element in
54+
Section(header: Text(element.key.description)) {
55+
ForEach(element.value, id: \.self) { item in
56+
label(item)
57+
#if os(watchOS)
58+
.onTapGesture {
59+
selected = item
60+
}
61+
#endif
6262
}
6363
}
6464
}
6565
}
66-
.toolbar {
67-
ToolbarItem(placement: .cancellationAction) {
68-
Button("Cancel") { showPresets = false }
69-
}
66+
.onChange(of: selected) { val in
67+
guard let val else { return }
68+
onSelect(val)
7069
}
7170
}
71+
72+
#if os(watchOS)
73+
private func platformView(content: () -> some View) -> some View {
74+
List {
75+
content()
76+
}
77+
}
78+
#endif
79+
80+
#if !os(watchOS)
81+
private func platformView(content: () -> some View) -> some View {
82+
List(selection: $selected) {
83+
content()
84+
}
85+
}
86+
#endif
7287
}
7388

74-
struct PresetsPicker_Previews: PreviewProvider {
89+
struct PresetsPickerSingle_Previews: PreviewProvider {
7590
struct TestHolder: View {
7691
let presets: OrderedDictionary = [
7792
"Machine/Free Weights": [
@@ -87,10 +102,11 @@ struct PresetsPicker_Previews: PreviewProvider {
87102
]
88103

89104
@State var showPresets = false
105+
@State var selected: String?
90106
var body: some View {
91107
NavigationStack {
92-
PresetsPicker(presets: presets, showPresets: $showPresets) {
93-
print("\(#function): Selected \($0) \($1)")
108+
PresetsPickerSingle(presets: presets) { _ in
109+
94110
} label: {
95111
Text($0.text)
96112
}

Sources/TextFieldPreset.swift

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ public struct TextFieldPreset<ButtonLabel, GroupKey, PresettedItem, PickerLabel>
3535
private let presets: PresetsDictionary
3636
private let buttonLabel: () -> ButtonLabel
3737
private let pickerLabel: (PresettedItem) -> PickerLabel
38-
private let onSelect: (GroupKey, PresettedItem) -> Void
38+
private let onSelect: (PresettedItem) -> Void
3939

4040
public init(_ text: Binding<String>,
4141
prompt: String,
4242
axis: Axis,
4343
presets: PresetsDictionary,
4444
@ViewBuilder buttonLabel: @escaping () -> ButtonLabel = { PresetButtonLabel() },
4545
@ViewBuilder pickerLabel: @escaping (PresettedItem) -> PickerLabel,
46-
onSelect: @escaping (GroupKey, PresettedItem) -> Void = { _, _ in })
46+
onSelect: @escaping (PresettedItem) -> Void = { _ in })
4747
{
4848
_text = text
4949
self.prompt = prompt
@@ -56,7 +56,8 @@ public struct TextFieldPreset<ButtonLabel, GroupKey, PresettedItem, PickerLabel>
5656

5757
// MARK: - Locals
5858

59-
@State private var showPresetNames = false
59+
@State private var isPresented = false
60+
@State private var selected: PresettedItem?
6061

6162
// MARK: - Views
6263

@@ -69,24 +70,29 @@ public struct TextFieldPreset<ButtonLabel, GroupKey, PresettedItem, PickerLabel>
6970
// .lineLimit(5)
7071
// #endif
7172

72-
Button(action: { showPresetNames = true }, label: buttonLabel)
73+
Button(action: { isPresented = true }, label: buttonLabel)
7374
.buttonStyle(.borderless)
7475
}
75-
.sheet(isPresented: $showPresetNames) {
76+
.sheet(isPresented: $isPresented) {
7677
NavigationStack {
77-
PresetsPicker(presets: presets,
78-
showPresets: $showPresetNames,
79-
onSelect: { groupKey, presetValue in
80-
// set the hard-coded name from the preset
81-
text = presetValue.text
82-
// allow caller to assign other fields
83-
onSelect(groupKey, presetValue)
84-
},
85-
label: pickerLabel)
78+
PresetsPickerSingle(presets: presets,
79+
onSelect: selectAction,
80+
label: pickerLabel)
81+
.toolbar {
82+
ToolbarItem(placement: .cancellationAction) {
83+
Button("Cancel") { isPresented = false }
84+
}
85+
}
8686
}
8787
.interactiveDismissDisabled() // NOTE: needed to prevent home button from dismissing sheet
8888
}
8989
}
90+
91+
private func selectAction(_ item: PresettedItem) {
92+
isPresented = false
93+
text = item.text
94+
onSelect(item)
95+
}
9096
}
9197

9298
struct TextFieldWithPresets_Previews: PreviewProvider {

0 commit comments

Comments
 (0)