Basically, I have a list of tags that I want to show inline but, when the tags are a lot, and their total width exceeds the parent view's width, I want to show a "+n" badge.
How to achieve the desired behavior starting from this basic code?
struct ParentView: View {
var body: some View {
HStack {
Tag("Apple")
Tag("Banana")
Tag("Cherry")
// more tags...
}
.frame(width: 200, alignment: .leading)
}
}
Any suggestions? Thanks in advance!
Find width of each of the badges.
width of badge = string's width + padding-x
Use this extension to find width of string
extension String {
func widthOfString(usingFont font: UIFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)) -> CGFloat {
let fontAttributes = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttributes)
return size.width
}
}
I have unsed the default font in iOS since I do not change the font of Text's.
Calculate the total width of badges that is less the screen's width.
struct ContentView: View {
let one = ["One"]
let two = ["One", "Two"]
let three = ["One", "Two", "Three"]
let four = ["One", "Two", "Three", "Four"]
let five = ["One", "Two", "Three", "Four", "Five"]
let six = ["One", "Two", "Three", "Four", "Five", "Six"]
let fruits = ["Apple", "Banana", "Orange", "Cherry", "Kiwi"]
let countries = ["France", "Spain", "Banana Republic", "USA", "Albania", "China", "England"]
let cities = ["Madrid", "Oslo", "Washington DC", "Istanbul", "Toronto", "Paris"]
let screenWidth = UIScreen.main.bounds.width // use another number if needed
var body: some View {
VStack(alignment: .leading) {
TagsView(tags: one, screenWidth: screenWidth)
TagsView(tags: two, screenWidth: screenWidth)
TagsView(tags: three, screenWidth: screenWidth)
TagsView(tags: four, screenWidth: screenWidth)
TagsView(tags: five, screenWidth: screenWidth)
TagsView(tags: six, screenWidth: screenWidth)
TagsView(tags: fruits, screenWidth: screenWidth)
TagsView(tags: countries, screenWidth: screenWidth)
TagsView(tags: cities, screenWidth: screenWidth)
}
}
}
struct TagsView: View {
let spacing: CGFloat = 8
let padding: CGFloat = 16
let tags: [String]
var width: CGFloat = .zero
var limit = 0
internal init(tags: [String], screenWidth: CGFloat) {
self.tags = tags
self.limit = tags.count
for (index, tag) in tags.enumerated() {
self.width += tag.widthOfString() + (2 * padding) + spacing
let remaining = "\(tags.count - index) +".widthOfString() + (2 * padding)
if width + remaining >= screenWidth {
self.limit = index
break
}
}
}
var body: some View {
HStack(spacing: spacing) {
ForEach(0..<limit, id: \.self) { index in
Tag(padding: padding, text: tags[index])
}
if limit > 0 && tags.count != limit {
Tag(padding: padding, text: "\(tags.count - limit) +")
}
}
.padding(.bottom)
}
}
struct Tag:View {
let padding: CGFloat
let text: String
let height: CGFloat = 20
let pVertical:CGFloat = 8
var body: some View {
Text(text)
.fixedSize()
.padding(.horizontal, padding)
.padding(.vertical, pVertical)
.background(Color.orange.opacity(0.2))
.foregroundColor(Color.orange)
.cornerRadius( (height + pVertical * 2) / 2)
.frame(height: height)
}
}
extension String {
func widthOfString(usingFont font: UIFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)) -> CGFloat {
let fontAttributes = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttributes)
return size.width
}
}