Skip to content

Commit fbcaaef

Browse files
committed
Implement optional wrapping for Pill Picker. Update example code. Add modifiers for selected icon, whether view should wrap or not, horizontal spacing, and vertical spacing
1 parent 0b4b0da commit fbcaaef

File tree

2 files changed

+86
-63
lines changed

2 files changed

+86
-63
lines changed

Example/PillPickerViewExample/PillPickerViewExample.swift

Lines changed: 75 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,48 +24,61 @@ struct ContentView: View {
2424
/// Required collection of items confirming to `Pill`
2525
/// which will be used for tracking which objects
2626
/// are selected
27-
@State private var selectedColors: [ColorPill] = []
27+
@State private var selectedGenres: [Genre] = []
2828

2929
var body: some View {
3030
NavigationView {
31-
TabView {
32-
33-
/// Example view where pills wrap to new line and only occupy
34-
/// necessary space
35-
ExampleBuilder(selectedColors: $selectedColors, content: {
36-
PillPickerView(items: colorPills, selectedPills: $selectedColors)
37-
.pillStackStyle(.wrap)
38-
})
39-
.tag(0)
40-
.navigationTitle("Wrapping pills")
41-
42-
/// Example view where pills do not wrap to new line and occupy
43-
/// set amount of space horizontally and vertically
44-
ExampleBuilder(selectedColors: $selectedColors, content: {
45-
PillPickerView(items: colorPills, selectedPills: $selectedColors)
46-
.pillStackStyle(.noWrap)
47-
})
48-
.tag(1)
49-
.navigationTitle("Static pills")
31+
VStack {
32+
TabView {
33+
34+
/// Example view where pills wrap to new line and only occupy
35+
/// necessary space
36+
ExampleBuilder(selectedItems: $selectedGenres, content: {
37+
PillPickerView(items: genres, selectedPills: $selectedGenres)
38+
.pillStackStyle(.wrap)
39+
})
40+
.tag(0)
41+
.navigationTitle("Wrapping pills")
42+
43+
/// Example view where pills do not wrap to new line and occupy
44+
/// set amount of space horizontally and vertically
45+
ExampleBuilder(selectedItems: $selectedGenres, content: {
46+
PillPickerView(items: genres, selectedPills: $selectedGenres)
47+
.pillStackStyle(.noWrap)
48+
})
49+
.tag(1)
50+
.navigationTitle("Static pills")
51+
}
52+
.tabViewStyle(.page)
53+
.tint(.accentColor)
5054
}
51-
.tabViewStyle(.page)
52-
.tint(.accentColor)
55+
.toolbar(content: {
56+
ToolbarItem(placement: .navigationBarTrailing, content: {
57+
Button(action: {
58+
withAnimation {
59+
selectedGenres.removeAll()
60+
}
61+
}, label: {
62+
Text("Clear All")
63+
})
64+
})
65+
})
5366
}
5467
}
5568
}
5669

57-
struct ExampleBuilder<V : View>: View {
70+
struct ExampleBuilder<T, V>: View where T: Pill, V: View {
5871

5972
typealias ContentGenerator = () -> V
6073

61-
@Binding var selectedColors: [ColorPill]
74+
@Binding var selectedItems: [T]
6275

6376
var content: ContentGenerator
6477

6578
var body: some View {
6679
ScrollView (showsIndicators: false) {
6780
HStack {
68-
Text("Select Your Favorite Colors")
81+
Text("Select Your Favorite Genres")
6982
.font(.system(size: 26, weight: .semibold, design: .rounded))
7083
Spacer()
7184
}
@@ -74,17 +87,26 @@ struct ExampleBuilder<V : View>: View {
7487
/// PillPickerView usage example
7588
content()
7689

77-
Text("Selected Colors:")
90+
Text("Selected Genres:")
7891
.font(.system(size: 20, weight: .semibold, design: .rounded))
7992
.padding(.top, 30)
8093

81-
HStack(spacing: 10) {
82-
ForEach(selectedColors, id: \.self) { colorPill in
83-
RoundedRectangle(cornerRadius: 40)
84-
.foregroundColor(colorPill.color)
85-
.frame(width: 40, height: 40)
94+
VStack(spacing: 10) {
95+
ForEach(selectedItems, id: \.self) { item in
96+
HStack {
97+
Text(item.title)
98+
.font(.system(size: 16, weight: .semibold, design: .rounded))
99+
.foregroundColor(.white)
100+
}
101+
.padding()
102+
.frame(maxWidth: .infinity)
103+
.background(
104+
RoundedRectangle(cornerRadius: 20)
105+
.foregroundColor(.accentColor)
106+
)
86107
}
87108
}
109+
.padding(.bottom, 60)
88110

89111
Spacer()
90112
}
@@ -94,18 +116,30 @@ struct ExampleBuilder<V : View>: View {
94116

95117
/// Sample model conforming to the `Pill` protocol.
96118
/// An element must have a `title` attribute.
97-
struct ColorPill: Pill {
119+
struct Genre: Pill {
98120
let title: String
99-
let color: Color
100121
}
101122

102123
/// Collection of items conforming to `Pill`
103-
let colorPills: [ColorPill] = [
104-
ColorPill(title: "Red", color: .red),
105-
ColorPill(title: "Green", color: .green),
106-
ColorPill(title: "Blue", color: .blue),
107-
ColorPill(title: "Yellow", color: .yellow),
108-
ColorPill(title: "Orange", color: .orange),
109-
ColorPill(title: "Purple", color: .purple),
110-
ColorPill(title: "Pink", color: .pink),
124+
let genres: [Genre] = [
125+
Genre(title: "Action"),
126+
Genre(title: "Adventure"),
127+
Genre(title: "Comedy"),
128+
Genre(title: "Drama"),
129+
Genre(title: "Fantasy"),
130+
Genre(title: "Horror"),
131+
Genre(title: "Mystery"),
132+
Genre(title: "Romance"),
133+
Genre(title: "Sci-Fi"),
134+
Genre(title: "Thriller"),
135+
Genre(title: "Western"),
136+
Genre(title: "Animation"),
137+
Genre(title: "Documentary"),
138+
Genre(title: "Historical"),
139+
Genre(title: "Musical"),
140+
Genre(title: "War"),
141+
Genre(title: "Crime"),
142+
Genre(title: "Family"),
143+
Genre(title: "Sports"),
144+
Genre(title: "Biography")
111145
]

Sources/PillPickerView.swift

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public protocol Pill: Equatable, Hashable {
4040

4141
// MARK: - Enums
4242

43+
/// The style the pill content has, whether
44+
/// it is dynamic and wrapping or statically placed
4345
public enum StackStyle {
4446
case wrap
4547
case noWrap
@@ -94,13 +96,8 @@ public struct PillOptions {
9496
/// Spacing applied horizontally between pills
9597
public var horizontalSpacing: CGFloat = 2
9698

97-
/// The alignment of the pills in the `PillPickerView`
98-
/// when not wrapping the content
99-
public var staticAlignment: HorizontalAlignment = .leading
100-
101-
/// The alignment of the pills in the `PillPickerView`
102-
/// when it is wrapping the content
103-
public var wrappingAlignment: Alignment = .topLeading
99+
/// Icon displayed in the pill when selected
100+
public var selectedIcon: Image = Image(systemName: "xmark")
104101
}
105102

106103
// MARK: - Main view
@@ -254,17 +251,9 @@ public extension PillPickerView {
254251
return view
255252
}
256253

257-
/// Set alignment of pills when statically placed
258-
func pillViewStaticAlignment(_ value: HorizontalAlignment) -> PillPickerView {
259-
var view = self
260-
view.options.staticAlignment = value
261-
return view
262-
}
263-
264-
/// Set alignment of pills when dynamically placed and wrapping
265-
func pillViewWrappingAlignment(_ value: Alignment) -> PillPickerView {
254+
func pillSelectedIcon(_ value: Image) -> PillPickerView {
266255
var view = self
267-
view.options.wrappingAlignment = value
256+
view.options.selectedIcon = value
268257
return view
269258
}
270259

@@ -301,7 +290,7 @@ struct PillView<T: Pill>: View {
301290
.font(options.font)
302291
.foregroundColor(pillForegroundColor)
303292
if isItemSelected() {
304-
Image(systemName: "xmark")
293+
options.selectedIcon
305294
.font(options.font)
306295
.foregroundColor(pillForegroundColor)
307296
.padding(.leading, 5)
@@ -389,7 +378,7 @@ struct PillItemStyle: ButtonStyle {
389378

390379

391380
/// Stack of pills not wrapping to a new line
392-
struct StaticStack<T, V>: View where T: Hashable, V: View {
381+
struct StaticStack<T, V>: View where T: Pill, V: View {
393382

394383
/// Alias for function type generating content
395384
typealias ContentGenerator = (T) -> V
@@ -440,7 +429,7 @@ struct StaticStack<T, V>: View where T: Hashable, V: View {
440429
viewGenerator(item)
441430
}
442431
}
443-
.frame(width: geometry.size.width, alignment: options.wrappingAlignment)
432+
.frame(width: geometry.size.width, alignment: .leading)
444433
}
445434
}
446435

@@ -484,7 +473,7 @@ extension Array {
484473
/// View which automatically wraps element
485474
/// in a HStack to a newline if it overflows
486475
/// horizontally and does not fit the screen dimensions
487-
public struct FlowStack<T, V>: View where T: Hashable, V: View {
476+
public struct FlowStack<T, V>: View where T: Pill, V: View {
488477

489478
// MARK: - Types and Properties
490479

@@ -519,7 +508,7 @@ public struct FlowStack<T, V>: View where T: Hashable, V: View {
519508
var width = CGFloat.zero
520509
var height = CGFloat.zero
521510

522-
return ZStack(alignment: options.wrappingAlignment) {
511+
return ZStack(alignment: .topLeading) {
523512
ForEach(items, id: \.self) { item in
524513
viewGenerator(item)
525514
.padding(.horizontal, options.horizontalSpacing)

0 commit comments

Comments
 (0)