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
}
}
深色外观
基本上,我有一个要内联显示的标签列表,但是,当标签很多,并且它们的总宽度超过父视图的宽度时,我想显示一个 "+ 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
}
}