在SwiftUI中实现标记列表
我试图在SwiftUI中实现标记列表,但我不确定如果列表水平溢出,如何让它将标记包装到其他行。我从一个名为tags的字符串数组开始,在SwiftUI I中循环遍历该数组并创建按钮,如下所示:在SwiftUI中实现标记列表,swiftui,Swiftui,我试图在SwiftUI中实现标记列表,但我不确定如果列表水平溢出,如何让它将标记包装到其他行。我从一个名为tags的字符串数组开始,在SwiftUI I中循环遍历该数组并创建按钮,如下所示: HStack{ ForEach(tags, id: \.self){tag in Button(action: {}) { HStack { Text(tag) Image(systemName:
HStack{
ForEach(tags, id: \.self){tag in
Button(action: {}) {
HStack {
Text(tag)
Image(systemName: "xmark.circle")
}
}
.padding()
.foregroundColor(.white)
.background(Color.orange)
.cornerRadius(.infinity)
.lineLimit(1)
}
}
如果标记数组很小,则按如下方式渲染:
但是,如果数组具有更多值,则会执行以下操作:
我要寻找的行为是将最后一个标记(黄色)换行到第二行。我意识到这是在一个HStack中,我希望我可以添加一个值大于1的对lineLimit的调用,但它似乎没有改变行为。如果我将外部HStack更改为VStack,它会将每个按钮放在单独的行上,因此仍然不是我尝试创建的行为。任何指导都将不胜感激。您可以尝试使用带水平轴的ScrollView
ScrollView(.horizontal, showsIndicators: true) {
HStack{
ForEach(tags, id: \.self){tag in
Button(action: {}) {
HStack {
Text(tag)
Image(systemName: "xmark.circle")
}
}
.padding()
.foregroundColor(.white)
.background(Color.orange)
.cornerRadius(.infinity)
.lineLimit(1)
}
}
}
希望这对你有帮助
如果您不想显示滚动指示器,也可以尝试使用
showsIndicators:false
。好的,这是我在这个网站上的第一个答案,如果我犯了某种堆栈溢出错误,请耐心等待
我将发布我的解决方案,该解决方案适用于这样一个模型:标签要么存在于selectedTags集合中,要么不存在于selectedTags集合中,所有可用的标签都存在于allTags集合中。在我的解决方案中,这些设置为绑定,因此可以从应用程序的其他位置注入。另外,我的解决方案将标签按字母顺序排列,因为这是最简单的。如果您想让它们以不同的方式排序,可能需要使用不同于两个独立集的模型
这肯定不会适用于每个人的用例,但因为我找不到自己的答案,而你的问题是我唯一能找到提及这个想法的地方,所以我决定尝试构建一些适合我的东西,并与你分享。希望有帮助:
struct TagList: View {
@Binding var allTags: Set<String>
@Binding var selectedTags: Set<String>
private var orderedTags: [String] { allTags.sorted() }
private func rowCounts(_ geometry: GeometryProxy) -> [Int] { TagList.rowCounts(tags: orderedTags, padding: 26, parentWidth: geometry.size.width) }
private func tag(rowCounts: [Int], rowIndex: Int, itemIndex: Int) -> String {
let sumOfPreviousRows = rowCounts.enumerated().reduce(0) { total, next in
if next.offset < rowIndex {
return total + next.element
} else {
return total
}
}
let orderedTagsIndex = sumOfPreviousRows + itemIndex
guard orderedTags.count > orderedTagsIndex else { return "[Unknown]" }
return orderedTags[orderedTagsIndex]
}
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
ForEach(0 ..< self.rowCounts(geometry).count, id: \.self) { rowIndex in
HStack {
ForEach(0 ..< self.rowCounts(geometry)[rowIndex], id: \.self) { itemIndex in
TagButton(title: self.tag(rowCounts: self.rowCounts(geometry), rowIndex: rowIndex, itemIndex: itemIndex), selectedTags: self.$selectedTags)
}
Spacer()
}.padding(.vertical, 4)
}
Spacer()
}
}
}
}
struct TagList_Previews: PreviewProvider {
static var previews: some View {
TagList(allTags: .constant(["one", "two", "three"]), selectedTags: .constant(["two"]))
}
}
extension String {
func widthOfString(usingFont font: UIFont) -> CGFloat {
let fontAttributes = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttributes)
return size.width
}
}
extension TagList {
static func rowCounts(tags: [String], padding: CGFloat, parentWidth: CGFloat) -> [Int] {
let tagWidths = tags.map{$0.widthOfString(usingFont: UIFont.preferredFont(forTextStyle: .headline))}
var currentLineTotal: CGFloat = 0
var currentRowCount: Int = 0
var result: [Int] = []
for tagWidth in tagWidths {
let effectiveWidth = tagWidth + (2 * padding)
if currentLineTotal + effectiveWidth <= parentWidth {
currentLineTotal += effectiveWidth
currentRowCount += 1
guard result.count != 0 else { result.append(1); continue }
result[result.count - 1] = currentRowCount
} else {
currentLineTotal = effectiveWidth
currentRowCount = 1
result.append(1)
}
}
return result
}
}
struct TagButton: View {
let title: String
@Binding var selectedTags: Set<String>
private let vPad: CGFloat = 13
private let hPad: CGFloat = 22
private let radius: CGFloat = 24
var body: some View {
Button(action: {
if self.selectedTags.contains(self.title) {
self.selectedTags.remove(self.title)
} else {
self.selectedTags.insert(self.title)
}
}) {
if self.selectedTags.contains(self.title) {
HStack {
Text(title)
.font(.headline)
}
.padding(.vertical, vPad)
.padding(.horizontal, hPad)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(radius)
.overlay(
RoundedRectangle(cornerRadius: radius)
.stroke(Color(UIColor.systemBackground), lineWidth: 1)
)
} else {
HStack {
Text(title)
.font(.headline)
.fontWeight(.light)
}
.padding(.vertical, vPad)
.padding(.horizontal, hPad)
.foregroundColor(.gray)
.overlay(
RoundedRectangle(cornerRadius: radius)
.stroke(Color.gray, lineWidth: 1)
)
}
}
}
}
struct标记列表:视图{
@绑定var-allTags:Set
@绑定变量selectedTags:Set
私有变量orderedTags:[字符串]{allTags.sorted()}
私有函数行计数(geometry:GeometryProxy)->[Int]{TagList.rowCounts(标记:orderedTags,padding:26,parentWidth:geometry.size.width)}
private func标记(行计数:[Int],行索引:Int,项索引:Int)->String{
让SumofPreiousRows=rowCounts.enumerated()。减少(0){total,下一个在
如果next.offset<行索引{
返回总计+下一个元素
}否则{
返回总数
}
}
让orderedTagsIndex=SumofPreiousRows+itemIndex
guard orderedTags.count>orderedTagsIndex else{return“[未知]”}
返回orderedTags[orderedTagsIndex]
}
var body:一些观点{
GeometryReader{中的几何体
VStack(对齐:。前导){
ForEach(0..CGFloat{
让fontAttributes=[NSAttributedString.Key.font:font]
让size=self.size(withAttributes:fontAttributes)
返回大小。宽度
}
}
扩展标记列表{
静态func行数(标记:[String],填充:CGFloat,parentWidth:CGFloat)->[Int]{
设tagWidths=tags.map{$0.widthOfString(使用字体:UIFont.preferredFont(forTextStyle:.headline))}
var currentLineTotal:CGFloat=0
var currentRowCount:Int=0
变量结果:[Int]=[]
用于标记宽度中的标记宽度{
让effectiveWidth=tagWidth+(2*填充)
如果currentLineTotal+effectiveWidth在他的博客中分享了一个不错的解决方案:
解决方案是一个名为FlexibleView
的自定义视图,它计算必要的行和HStack
,以放置给定元素,并在需要时将它们包装成多行
struct\u FlexibleView:View where Data.Element:Hashable{
让可用宽度为:CGFloat
让数据:数据
让间距:CGFloat
let对齐:水平对齐
让内容:(Data.Element)->content
@状态变量elementsSize:[Data.Element:CGSize]=[:]
var body:一些观点{
VStack(对齐:对齐,间距:间距){
ForEach(computeRows(),id:\.self){rowElements in
HStack(间距:间距){
ForEach(rowElements,id:\.self){中的元素
内容(要素)
.fixedSize()
.readSize{size in
elementsSize[元素]=大小
}
}
}
}
}
}
func computeRows()->[[Data.Element]]{
变量行:[[Data.Element]=[[]]
var currentRow=0
var剩余宽度=可用宽度
对于数据中的元素{
let elementSize=elementsSize[元素,默认值:CGSize(宽度:availableWidth,高度:1)]
如果剩余宽度-(elementSize.width+间距)>=0{
行[currentRow]。追加(元素)
}否则{
currentRow=currentRow+1
行。追加([元素])
剩余宽度=可用宽度
}
remainingWidth=remainingWidth-(elementSize.width+间距)
}
返回行
}
}
用法:
FlexibleView(
数据:[
“这里有”“to”“the”“疯狂”“one”“the”“misfits”“the”“the”“叛军”“the”