1
votes

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 -

enter image description here

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.

I know this has been discussed on SO within the last month or so, but I'm having trouble figuring out the right search terms to find the post...jnpdx