I would like to create a horizontal collection of views that fills the width of it's container.
If the total number of items is wider than the container, instead of wrapping I'd like it to truncate and instead show a count of the additional items.
I have attempted to create this with the following view
struct HorizontalTagsView: View {
private let tags: [String]
private let spacing: CGFloat = 8
private let interItemSpacing: CGFloat = 6
private let overflowWidth: CGFloat = 12
private let font = UIFont.preferredFont(for: .footnote, weight: .light)
init(tags: [String]) {
self.tags = tags
}
var body: some View {
GeometryReader { proxy in
let (tags, overflow) = generateTags(for: tags, using: proxy, font: font)
HStack(alignment: .center, spacing: spacing) {
ForEach(tags, id: \.self) { tag in
makeTextView(str: tag)
}
if overflow > 0 {
makeTextView(str: "+ \(overflow)")
}
}
}
}
}
private extension HorizontalTagsView {
func generateTags(for values: [String], using proxy: GeometryProxy, font: UIFont) -> (tags: [String], overflow: Int) {
var output: [String] = []
var currentWidth: CGFloat = .zero
let maxWidth: CGFloat = proxy.size.width - overflowWidth
values.forEach { value in
let size = value.size(withAttributes: [.font: font])
let width = size.width + (interItemSpacing * 2)
if width + currentWidth < maxWidth {
currentWidth += currentWidth + width + (spacing * 2)
output.append(value)
}
}
return (output, values.count - output.count)
}
@ViewBuilder func makeTextView(str: String) -> some View {
Text(str)
.font(Font(font))
.padding(.vertical, 2)
.padding(.horizontal, interItemSpacing)
.background(Color.gray)
.cornerRadius(4)
}
}
struct HorizontalTagsView_Previews: PreviewProvider {
static var previews: some View {
HorizontalTagsView(tags: (0 ..< 10).map { "Tag \($0)" })
.padding()
}
}
extension UIFont {
static func preferredFont(for style: TextStyle, weight: Weight, italic: Bool = false, size: CGFloat? = nil) -> UIFont {
let traits = UITraitCollection(preferredContentSizeCategory: .large)
let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style, compatibleWith: traits)
var font = UIFont.systemFont(ofSize: size ?? desc.pointSize, weight: weight)
let metrics = UIFontMetrics(forTextStyle: style)
return metrics.scaledFont(for: font)
}
}
However the rows breaks off far before the width of the screen -
I would expect it to show the full row without the "+" indicator if all the items fit or show as many items as can fit, including the "+" indicator.