I'm not so familiar with SwfitUI.
I found this helper to achieve this tag view:
But making it selectable is pain & for everything I did, I got many errors...
How can I make this thing work!?
Here is my complete class:
import SwiftUI
struct TagViewItem: Hashable {
var title: String
var isSelected: Bool
static func == (lhs: TagViewItem, rhs: TagViewItem) -> Bool {
return lhs.isSelected == rhs.isSelected
}
func hash(into hasher: inout Hasher) {
hasher.combine(title)
hasher.combine(isSelected)
}
}
struct TagView: View {
@State var tags: [TagViewItem]
@State private var totalHeight = CGFloat.zero // << variant for ScrollView/List // = CGFloat.infinity // << variant for VStack
var body: some View {
VStack {
GeometryReader { geometry in
self.generateContent(in: geometry)
}
}
.frame(height: totalHeight)// << variant for ScrollView/List
//.frame(maxHeight: totalHeight) // << variant for VStack
}
private func generateContent(in g: GeometryProxy) -> some View {
var width = CGFloat.zero
var height = CGFloat.zero
return ZStack(alignment: .topLeading) {
ForEach(tags.indices) { index in
item(for: tags[index].title, isSelected: &tags[index].isSelected)
.padding([.horizontal, .vertical], 4)
.alignmentGuide(.leading, computeValue: { d in
if (abs(width - d.width) > g.size.width) {
width = 0
height -= d.height
}
let result = width
if tag == self.tags.last! {
width = 0 //last item
} else {
width -= d.width
}
return result
})
.alignmentGuide(.top, computeValue: {d in
let result = height
if tag == self.tags.last! {
height = 0 // last item
}
return result
})
}
}.background(viewHeightReader($totalHeight))
}
private func item(for text: String, isSelected: inout Bool) -> some View {
Text(text)
.foregroundColor(isSelected ? Colors.primaryBarBackground : Colors.textColor)
.padding()
.lineLimit(1)
.background(isSelected ? Colors.primaryBlue : Colors.primaryBarBackground)
.frame(height: 36)
.cornerRadius(18)
.overlay(Capsule().stroke(Colors.primaryBlue, lineWidth: 4))
.onTapGesture {
isSelected.toggle()
}
}
private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
return GeometryReader { geometry -> Color in
let rect = geometry.frame(in: .local)
DispatchQueue.main.async {
binding.wrappedValue = rect.size.height
}
return .clear
}
}
}
Finally, after wrestling with it a lot, it is the working version:
struct TagView: View {
@State var tags: [TagViewItem]
@State private var totalHeight = CGFloat.zero // << variant for ScrollView/List // = CGFloat.infinity // << variant for VStack
var body: some View {
VStack {
GeometryReader { geometry in
self.generateContent(in: geometry)
}
}
.frame(height: totalHeight)// << variant for ScrollView/List
//.frame(maxHeight: totalHeight) // << variant for VStack
}
private func generateContent(in g: GeometryProxy) -> some View {
var width = CGFloat.zero
var height = CGFloat.zero
return ZStack(alignment: .topLeading) {
ForEach(tags.indices) { index in
item(for: tags[index].title, isSelected: tags[index].isSelected)
.padding([.horizontal, .vertical], 4)
.alignmentGuide(.leading, computeValue: { d in
if (abs(width - d.width) > g.size.width) {
width = 0
height -= d.height
}
let result = width
if tags[index].title == self.tags.last!.title {
width = 0 //last item
} else {
width -= d.width
}
return result
})
.alignmentGuide(.top, computeValue: {d in
let result = height
if tags[index].title == self.tags.last!.title {
height = 0 // last item
}
return result
}).onTapGesture {
tags[index].isSelected.toggle()
}
}
}.background(viewHeightReader($totalHeight))
}
private func item(for text: String, isSelected: Bool) -> some View {
Text(text)
.foregroundColor(isSelected ? Colors.primaryBarBackground : Colors.textColor)
.padding()
.lineLimit(1)
.background(isSelected ? Colors.primaryBlue : Colors.primaryBarBackground)
.frame(height: 36)
.cornerRadius(18)
.overlay(Capsule().stroke(Colors.primaryBlue, lineWidth: 1))
}
private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
return GeometryReader { geometry -> Color in
let rect = geometry.frame(in: .local)
DispatchQueue.main.async {
binding.wrappedValue = rect.size.height
}
return .clear
}
}
}
Usage:
TagView(tags: [TagViewItem(title: "ff", isSelected: false), TagViewItem(title: "yyhuuuh", isSelected: false), TagViewItem(title: "kjhgdtfyughuihu", isSelected: true), TagViewItem(title: "nbyvyvuyv", isSelected: false)])