Skip to content

Commit 0b4b0da

Browse files
committed
Work on adding alignments to both static and wrapping views
1 parent c13bd4d commit 0b4b0da

File tree

8 files changed

+143
-70
lines changed

8 files changed

+143
-70
lines changed

Assets/Header.png

-3.22 KB
Loading

Assets/Logo.png

725 Bytes
Loading

Example/PillPickerViewExample/Assets.xcassets/AccentColor.colorset/Contents.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
{
22
"colors" : [
33
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0x6F",
9+
"green" : "0x52",
10+
"red" : "0xEA"
11+
}
12+
},
413
"idiom" : "universal"
514
}
615
],

Example/PillPickerViewExample/Assets.xcassets/AppIcon.appiconset/Contents.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"images" : [
33
{
4-
"filename" : "PillPickerViewLogo.png",
4+
"filename" : "Logo.png",
55
"idiom" : "universal",
66
"platform" : "ios",
77
"size" : "1024x1024"
23.5 KB
Loading
Binary file not shown.

Example/PillPickerViewExample/PillPickerViewExample.swift

Lines changed: 74 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,55 +21,91 @@ struct PillPickerViewExample: App {
2121

2222
struct ContentView: View {
2323

24-
/// Sample model conforming to the `Pill` protocol.
25-
/// An element must have a `title` attribute.
26-
struct ColorPill: Pill {
27-
let title: String
28-
let color: Color
29-
}
30-
3124
/// Required collection of items confirming to `Pill`
3225
/// which will be used for tracking which objects
3326
/// are selected
3427
@State private var selectedColors: [ColorPill] = []
3528

36-
/// Collection of items conforming to `Pill`
37-
let colorPills: [ColorPill] = [
38-
ColorPill(title: "Red", color: .red),
39-
ColorPill(title: "Green", color: .green),
40-
ColorPill(title: "Blue", color: .blue),
41-
ColorPill(title: "Yellow", color: .yellow),
42-
ColorPill(title: "Orange", color: .orange),
43-
ColorPill(title: "Purple", color: .purple),
44-
ColorPill(title: "Pink", color: .pink),
45-
]
46-
4729
var body: some View {
4830
NavigationView {
49-
VStack(alignment: .leading, spacing: 40) {
50-
Text("Select Your Favorite Colors")
51-
.font(.system(size: 26, weight: .semibold, design: .rounded))
52-
53-
/// PillPickerView usage example
54-
PillPickerView(items: colorPills, selectedPills: $selectedColors)
55-
.pillStackStyle(StackStyle.noWrap)
31+
TabView {
5632

57-
Text("Selected Colors:")
58-
.font(.system(size: 20, weight: .semibold, design: .rounded))
59-
60-
HStack(spacing: 10) {
61-
ForEach(selectedColors, id: \.self) { colorPill in
62-
RoundedRectangle(cornerRadius: 40)
63-
.foregroundColor(colorPill.color)
64-
.frame(width: 40, height: 40)
65-
}
66-
}
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")
6741

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")
50+
}
51+
.tabViewStyle(.page)
52+
.tint(.accentColor)
53+
}
54+
}
55+
}
56+
57+
struct ExampleBuilder<V : View>: View {
58+
59+
typealias ContentGenerator = () -> V
60+
61+
@Binding var selectedColors: [ColorPill]
62+
63+
var content: ContentGenerator
64+
65+
var body: some View {
66+
ScrollView (showsIndicators: false) {
67+
HStack {
68+
Text("Select Your Favorite Colors")
69+
.font(.system(size: 26, weight: .semibold, design: .rounded))
6870
Spacer()
6971
}
70-
.padding()
71-
.padding(.top, 15)
72-
.navigationTitle("PillPickerView")
72+
.padding(.vertical, 30)
73+
74+
/// PillPickerView usage example
75+
content()
76+
77+
Text("Selected Colors:")
78+
.font(.system(size: 20, weight: .semibold, design: .rounded))
79+
.padding(.top, 30)
80+
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)
86+
}
87+
}
88+
89+
Spacer()
7390
}
91+
.padding(.horizontal, 15)
7492
}
7593
}
94+
95+
/// Sample model conforming to the `Pill` protocol.
96+
/// An element must have a `title` attribute.
97+
struct ColorPill: Pill {
98+
let title: String
99+
let color: Color
100+
}
101+
102+
/// 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),
111+
]

Sources/PillPickerView.swift

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ public extension PillPickerView {
270270

271271
}
272272

273-
// MARK: - Child views
273+
// MARK: - Pill View
274274

275275
/// View containing the selectable element
276276
struct PillView<T: Pill>: View {
@@ -405,34 +405,63 @@ struct StaticStack<T, V>: View where T: Hashable, V: View {
405405
/// Chunk size which `items` is divided into
406406
@State private var chunkSize: Int = 1
407407

408+
/// Current total height calculated
409+
@State private var totalHeight = CGFloat.zero
410+
408411
private func calculateChunkSize(geometry: GeometryProxy) {
409412
let availableWidth = geometry.size.width
410413
let itemWidth: CGFloat = 100
411414

412-
chunkSize = max(Int(availableWidth / itemWidth), 1)
415+
chunkSize = max(Int(availableWidth / (itemWidth + options.horizontalSpacing)), 1)
416+
}
417+
418+
// MARK: - Height Calculation
419+
420+
/// Used to calculate the total height of the view. It wraps the ZStack
421+
/// in a GeometryReader to obtain the height of the content and updates
422+
/// the `totalHeight` state variable accordingly.
423+
private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
424+
return GeometryReader { geometry -> Color in
425+
let rect = geometry.frame(in: .local)
426+
DispatchQueue.main.async {
427+
binding.wrappedValue = rect.size.height
428+
}
429+
return .clear
430+
}
413431
}
414432

415433
var body: some View {
416-
GeometryReader { geometry in
417-
VStack(alignment: options.staticAlignment, spacing: options.verticalSpacing) {
418-
ForEach(items.chunked(into: chunkSize), id: \.self) { chunk in
419-
HStack(spacing: options.horizontalSpacing) {
420-
ForEach(chunk, id: \.self) { item in
421-
viewGenerator(item)
434+
VStack {
435+
GeometryReader { geometry in
436+
VStack(spacing: options.verticalSpacing) {
437+
ForEach(items.chunked(into: chunkSize), id: \.self) { chunk in
438+
HStack(spacing: options.horizontalSpacing) {
439+
ForEach(chunk, id: \.self) { item in
440+
viewGenerator(item)
441+
}
422442
}
443+
.frame(width: geometry.size.width, alignment: options.wrappingAlignment)
423444
}
424445
}
425-
}
426-
.onAppear {
427-
calculateChunkSize(geometry: geometry)
428-
}
429-
430-
/// Dynamically generate chunk size based on
431-
/// screen direction and dimension
432-
.onChange(of: geometry.size.width) { _ in
433-
calculateChunkSize(geometry: geometry)
446+
447+
/// Necessary to get generated height
448+
/// of child elements combined, then
449+
/// set the parent `VStack` height accordingly
450+
.background(viewHeightReader($totalHeight))
451+
452+
.onAppear {
453+
calculateChunkSize(geometry: geometry)
454+
}
455+
456+
/// Dynamically generate chunk size based on
457+
/// screen direction and dimension
458+
.onChange(of: geometry.size.width) { _ in
459+
calculateChunkSize(geometry: geometry)
460+
}
434461
}
435462
}
463+
.frame(height: totalHeight)
464+
.frame(maxWidth: .infinity)
436465
}
437466
}
438467

@@ -481,7 +510,7 @@ public struct FlowStack<T, V>: View where T: Hashable, V: View {
481510
generateContent(in: geometry)
482511
}
483512
}
484-
.frame(maxHeight: totalHeight)
513+
.frame(height: totalHeight)
485514
}
486515

487516
// MARK: - Content Generation
@@ -512,7 +541,7 @@ public struct FlowStack<T, V>: View where T: Hashable, V: View {
512541
/// to 0 and subtracts the item's height from height to move to the next row.
513542
/// Otherwise, it returns the current `width` value and updates `width` by subtracting the item's width.
514543
func calculateLeadingAlignment(dimension: ViewDimensions, item: T) -> CGFloat {
515-
if abs(width - (dimension.width + dimension.width / 2)) > geometry.size.width {
544+
if abs(width - dimension.width) > geometry.size.width {
516545
width = 0
517546
height -= dimension.height
518547
}
@@ -537,19 +566,18 @@ public struct FlowStack<T, V>: View where T: Hashable, V: View {
537566
}
538567

539568
}
569+
}
540570

541-
// MARK: - Height Calculation
542-
543-
/// Used to calculate the total height of the view. It wraps the ZStack
544-
/// in a GeometryReader to obtain the height of the content and updates
545-
/// the `totalHeight` state variable accordingly.
546-
private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
547-
return GeometryReader { geometry -> Color in
548-
let rect = geometry.frame(in: .local)
549-
DispatchQueue.main.async {
550-
binding.wrappedValue = rect.size.height
551-
}
552-
return .clear
571+
/// MARK: - Utility functions
572+
573+
/// Get height of context and set passed binding
574+
/// parameter based on received value
575+
func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
576+
return GeometryReader { geometry -> Color in
577+
let rect = geometry.frame(in: .local)
578+
DispatchQueue.main.async {
579+
binding.wrappedValue = rect.size.height
553580
}
581+
return .clear
554582
}
555583
}

0 commit comments

Comments
 (0)