Skip to content

Commit 2357b0e

Browse files
committed
Deprecate flow layouts and simplify tag lists
1 parent 364d397 commit 2357b0e

File tree

7 files changed

+208
-260
lines changed

7 files changed

+208
-260
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Once a type implements ``Taggable``, it can make use of a lot of automatically i
7777

7878
### Views
7979

80-
TagKit has a bunch of tag related types, like ``TagCapsule``, ``TagList``, ``TagEditList`` and ``TagTextField``.
80+
TagKit has a couple of tag related types, like ``TagList``, ``TagEditList`` and ``TagTextField``.
8181

8282

8383

RELEASE_NOTES.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@ TagKit will use semver after 1.0.
66

77
## 0.5
88

9-
This version makes TagKit use Swift 6.
9+
This version makes TagKit use Swift 6, by removing the flow layout parts from the library.
10+
11+
The `TagList` and `TagEditList` is therefore much simpler now before, and can be used in more ways.
12+
13+
### 💡 Behavior Changes
14+
15+
* `TagList` and `TagEditList` now just lists the provided tags.
16+
* `TagList` and `TagEditList` can now be rendered in any layout container.
1017

1118
### 🗑️ Deprecated
1219

1320
* `Slugifiable` has been deprecated. Just use the `slugified` string extension instead.
21+
* `TagCapsule` has been deprecated, since it's better to just customize a regular `Text`.
1422

1523
### 💥 Breaking Changes
1624

Sources/TagKit/Views/TagEditList.swift

Lines changed: 29 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -8,73 +8,45 @@
88

99
import SwiftUI
1010

11-
/**
12-
This view lists tags in a leading to trailing flow and lets
13-
you tap tags to add and remove them from a provided binding.
14-
15-
The view takes a list of tags and use a tag view builder to
16-
render a view for each tag. You can use any custom tag view,
17-
for instance a ``TagCapsule``.
18-
19-
The view can take a list of additional tags. Tho avoid that
20-
tags disappear from the list when you toggle them off. Make
21-
sure to use the `additionalTags` parameter to specify which
22-
tags you always want to show in the list.
23-
24-
You must specify a `container`, since this list is rendered
25-
differently depending on if it's added to a `ScrollView` or
26-
a `VerticalStack`.
27-
*/
11+
/// This view lists a collection of tags, that can be tapped
12+
/// to toggle them in the provided tags binding.
13+
///
14+
/// This view will list all tags in the provided binding, as
15+
/// well as a list of additional tags which should be listed
16+
/// even when they are not set in the binding.
17+
///
18+
/// Note that this list only renders the tag views. You must
19+
/// specify the container in which they will be rendered.
2820
public struct TagEditList<TagView: View>: View {
2921

3022
/// Create a tag edit list.
3123
///
3224
/// - Parameters:
3325
/// - tags: The items to render in the layout.
3426
/// - additionalTags: Additional tags to pick from.
35-
/// - container: The container type, by default `.scrollView`.
36-
/// - horizontalSpacing: The horizontal spacing between items.
37-
/// - verticalSpacing: The vertical spacing between items.
3827
/// - tagView: The tag view builder.
3928
public init(
4029
tags: Binding<[String]>,
4130
additionalTags: [String],
42-
container: TagListContainer = .scrollView,
43-
horizontalSpacing: CGFloat = 5,
44-
verticalSpacing: CGFloat = 5,
4531
@ViewBuilder tagView: @escaping TagViewBuilder
4632
) {
4733
self.tags = tags
4834
self.additionalTags = additionalTags
49-
self.container = container
50-
self.horizontalSpacing = horizontalSpacing
51-
self.verticalSpacing = verticalSpacing
5235
self.tagView = tagView
53-
let initialHeight: CGFloat = container == .scrollView ? .zero : .infinity
54-
_totalHeight = State(initialValue: initialHeight)
5536
}
5637

5738
private let tags: Binding<[String]>
5839
private let additionalTags: [String]
59-
private let container: TagListContainer
60-
private let horizontalSpacing: CGFloat
61-
private let verticalSpacing: CGFloat
62-
40+
6341
@ViewBuilder
6442
private let tagView: TagViewBuilder
6543

6644
/// This type defines the tag view builder for the list.
6745
public typealias TagViewBuilder = (_ tag: String, _ hasTag: Bool) -> TagView
6846

69-
@State
70-
private var totalHeight: CGFloat
71-
7247
public var body: some View {
7348
TagList(
7449
tags: allTags,
75-
container: container,
76-
horizontalSpacing: horizontalSpacing,
77-
verticalSpacing: verticalSpacing
7850
) { tag in
7951
Button(action: { toggleTag(tag) }) {
8052
tagView(tag, hasTag(tag))
@@ -124,104 +96,60 @@ private extension TagEditList {
12496

12597
struct Preview: View {
12698

127-
@State
128-
var newTag = ""
129-
130-
@State
131-
var tags = ["tag-1"]
132-
133-
@State
134-
var added: [String] = []
99+
@State var newTag = ""
100+
@State var tags = ["tag-1"]
101+
102+
let slugConfiguration = SlugConfiguration.standard
135103

136104
var body: some View {
137105
NavigationView {
138106
ScrollView {
139-
VStack(alignment: .leading, spacing: 30) {
140-
list("Standard Style", .standard, .standardSelected)
141-
list("Custom Style", .custom, .customSelected)
107+
TagEditList(
108+
tags: $tags,
109+
additionalTags: ["always-visible"]
110+
) { tag, isAdded in
111+
Text(tag.slugified())
112+
.font(.system(size: 12))
113+
.foregroundColor(.black)
114+
.padding(.horizontal, 8)
115+
.padding(.vertical, 4)
116+
.background(isAdded ? Color.green : Color.primary.opacity(0.1), in: .capsule)
142117
}
143118
.padding()
144-
145119
}
146-
// .background(
147-
// LinearGradient(
148-
// colors: [.red, .blue],
149-
// startPoint: .top,
150-
// endPoint: .bottom
151-
// )
152-
// )
153120
.toolbar {
154121
ToolbarItem {
155122
HStack {
156123
TagTextField(
157124
text: $newTag,
158-
placeholder: "Add new tag"
125+
placeholder: "Add new tag",
126+
configuration: slugConfiguration
159127
)
160128
#if os(iOS)
161129
.autocorrectionDisabled()
162130
.textFieldStyle(.roundedBorder)
163131
#endif
164132
Button("Add") {
165-
addTag(tag: newTag)
133+
addNewTag(tag: newTag)
166134
}
167135
.disabled(newTag.isEmpty)
168136
}
169137
}
170138
}
171139
}
172140
}
173-
174-
private func list(
175-
_ title: String,
176-
_ style: TagCapsuleStyle,
177-
_ selected: TagCapsuleStyle
178-
) -> some View {
179-
VStack(alignment: .leading) {
180-
Text(title)
181-
.font(.footnote)
182-
183-
TagEditList(
184-
tags: $tags,
185-
additionalTags: ["tag-1", "tag-2", "tag-3"] + added
186-
) { tag, hasTag in
187-
TagCapsule(tag)
188-
.tagCapsuleStyle(hasTag ? selected : style)
189-
}
190-
}
191-
}
192141

193-
private func addTag(
142+
private func addNewTag(
194143
tag: String,
195144
selected: Bool = true
196145
) {
197-
let slug = tag.slugified()
146+
let slug = tag.slugified(with: slugConfiguration)
198147
if selected {
199148
tags.append(slug)
200149
}
201-
added.append(slug)
202150
newTag = ""
203151
}
204152
}
205153

206154
return Preview()
207155
}
208-
209-
private extension TagCapsuleStyle {
210-
211-
static var custom: TagCapsuleStyle {
212-
.init(
213-
foregroundColor: .black,
214-
backgroundColor: .red,
215-
border: .init(width: 4)
216-
)
217-
}
218-
219-
static var customSelected: TagCapsuleStyle {
220-
.init(
221-
foregroundColor: .black,
222-
backgroundColor: .red,
223-
border: .init(width: 4),
224-
shadow: .standardSelected
225-
)
226-
}
227-
}

Sources/TagKit/Views/TagList.swift

Lines changed: 19 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,15 @@ public enum TagListContainer {
1616
case scrollView, vstack
1717
}
1818

19-
/**
20-
This view lists tags in a leading to trailing flow.
21-
22-
The view takes a list of tags and use the provided tag view
23-
builder to render a view for each tag. This gives you a lot
24-
of flexibility, since you can use any view you want.
25-
26-
Note that the list will not slugify the provided tags since
27-
this would require it to use the same slug configuration as
28-
was used to generate the tags. Just provide it with already
29-
slugified tags.
30-
31-
You must specify a container type, since the list has to be
32-
rendered differently depending on if it's in a `ScrollView`
33-
or a `VerticalStack`.
34-
*/
19+
/// This view can be used to list a collection of tags.
20+
///
21+
/// The view takes a list of tags and renders them using the
22+
/// provided `tagView` builder. It will not slugify the tag
23+
/// elements, so either provide already slugified strings or
24+
/// slugify them in the view builder.
25+
///
26+
/// Note that this list only renders the tag views. You must
27+
/// specify the container in which they will be rendered.
3528
public struct TagList<TagView: View>: View {
3629

3730
/// Create a tag list.
@@ -44,116 +37,41 @@ public struct TagList<TagView: View>: View {
4437
/// - tagView: The item view builder.
4538
public init(
4639
tags: [String],
47-
container: TagListContainer = .scrollView,
48-
horizontalSpacing: CGFloat = 5,
49-
verticalSpacing: CGFloat = 5,
5040
@ViewBuilder tagView: @escaping TagViewBuilder
5141
) {
5242
self.tags = tags
53-
self.container = container
54-
self.horizontalSpacing = horizontalSpacing
55-
self.verticalSpacing = verticalSpacing
5643
self.tagView = tagView
57-
let initialHeight: CGFloat = container == .scrollView ? .zero : .infinity
58-
_totalHeight = State(initialValue: initialHeight)
5944
}
6045

6146
private let tags: [String]
62-
private let container: TagListContainer
63-
private let horizontalSpacing: CGFloat
64-
private let verticalSpacing: CGFloat
6547

6648
@ViewBuilder
6749
private let tagView: TagViewBuilder
6850

6951
/// This type defines the tag view builder for the list.
7052
public typealias TagViewBuilder = (_ tag: String) -> TagView
7153

72-
@State
73-
private var totalHeight: CGFloat
74-
7554
public var body: some View {
76-
if container == .scrollView {
77-
content.frame(height: totalHeight)
78-
} else {
79-
content.frame(maxHeight: totalHeight)
80-
}
81-
}
82-
}
83-
84-
@MainActor
85-
private extension TagList {
86-
87-
var content: some View {
88-
GeometryReader { geometry in
89-
content(in: geometry)
90-
}
91-
}
92-
93-
func content(in g: GeometryProxy) -> some View {
94-
var width = CGFloat.zero
95-
var height = CGFloat.zero
96-
var lastHeight = CGFloat.zero
97-
let itemCount = tags.count
98-
return ZStack(alignment: .topLeading) {
99-
ForEach(Array(tags.enumerated()), id: \.offset) { index, item in
100-
tagView(item)
101-
.padding([.horizontal], horizontalSpacing)
102-
.padding([.vertical], verticalSpacing)
103-
.alignmentGuide(.leading, computeValue: { d in
104-
if abs(width - d.width) > g.size.width {
105-
width = 0
106-
height -= lastHeight
107-
}
108-
lastHeight = d.height
109-
let result = width
110-
if index == itemCount - 1 {
111-
width = 0
112-
} else {
113-
width -= d.width
114-
}
115-
return result
116-
})
117-
.alignmentGuide(.top, computeValue: { _ in
118-
let result = height
119-
if index == itemCount - 1 {
120-
height = 0
121-
}
122-
return result
123-
})
124-
}
125-
}
126-
.background(viewHeightReader($totalHeight))
127-
}
128-
129-
func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
130-
return GeometryReader { geo -> Color in
131-
DispatchQueue.main.async {
132-
binding.wrappedValue = geo.frame(in: .local).size.height
133-
}
134-
return .clear
55+
ForEach(Array(tags.enumerated()), id: \.offset) {
56+
tagView($0.element)
13557
}
13658
}
13759
}
13860

13961
#Preview {
14062

141-
let tags = [
142-
"A long text here", "Another long text here",
143-
"A", "bunch", "of", "short", "texts", "in", "a", "row",
144-
"And then a very very very long long long long long long long long longlong long long long long long longlong long long long long long longlong long long long long long longlong long long long long long longlong long long long long long long long one",
145-
"and", "then", "some", "more", "short", "ones"
146-
]
147-
148-
return ScrollView {
149-
TagList(tags: tags) { tag in
150-
Text(tag)
63+
ScrollView {
64+
TagList(tags: [
65+
"A long tag here",
66+
"Another long tag here",
67+
"A", "bunch", "of", "short", "tags"
68+
]) { tag in
69+
Text(tag.slugified())
15170
.font(.system(size: 12))
15271
.foregroundColor(.black)
15372
.padding(.horizontal, 8)
15473
.padding(.vertical, 4)
155-
.background(Color.green)
156-
.clipShape(RoundedRectangle(cornerRadius: 5))
74+
.background(Color.green, in: .rect(cornerRadius: 5))
15775
}
15876
.padding()
15977
}

0 commit comments

Comments
 (0)