SwiftUI - 当内联元素宽度超过父宽度时更多 n 徽章

SwiftUI - More n badge when inline elements width exceed parent width

基本上,我有一个要内联显示的标签列表,但是,当标签很多,并且它们的总宽度超过父视图的宽度时,我想显示一个 "+ n 徽章。

如何从这个基本代码开始实现所需的行为?

struct ParentView: View {
   var body: some View {
      HStack {
         Tag("Apple")
         Tag("Banana")
         Tag("Cherry")
         // more tags...
      }
      .frame(width: 200, alignment: .leading)
   }
}

有什么建议吗?提前致谢!

1.

找出每个徽章的宽度。

width of badge = string's width + padding-x

2.

使用此扩展来查找字符串的宽度

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
    }
}

我已经取消了 iOS 中的默认字体,因为我没有更改文本的字体。

3.

计算徽章总宽度减去屏幕宽度。



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
    }
}


深​​色外观