43

是否有可能完全显示蓝色标签(当前被截断)然后自动换行?

NavigationLink(destination: GameListView()) {
  VStack(alignment: .leading, spacing: 5){
    // Name der Sammlung:
    Text(collection.name)
      .font(.headline)

    // Optional: Für welche Konsolen bzw. Plattformen:
    HStack(alignment: .top, spacing: 10){
      ForEach(collection.platforms, id: \.self) { platform in
        Text(platform)
          .padding(.all, 5)
          .font(.caption)
          .background(Color.blue)
          .foregroundColor(Color.white)
          .cornerRadius(5)
          .lineLimit(1)
      }
    }
  }
  .padding(.vertical, 10)
}

在此处输入图像描述

此外,蓝色标签中不应有换行符:

在此处输入图像描述

这就是它最终的样子:

在此处输入图像描述

4

6 回答 6

46

以下是如何使用alignmentGuide(s) 完成此操作的一些方法。它被简化以避免许多代码发布,但希望它有用。

更新:在我对带有换行和动态高度的 SwiftUI HStack 的回答中,还有以下解决方案的更新和改进变体

这是结果:

swiftui包装布局

这是完整的演示代码(自动支持方向):

import SwiftUI

struct TestWrappedLayout: View {
    @State var platforms = ["Ninetendo", "XBox", "PlayStation", "PlayStation 2", "PlayStation 3", "PlayStation 4"]

    var body: some View {
        GeometryReader { geometry in
            self.generateContent(in: geometry)
        }
    }

    private func generateContent(in g: GeometryProxy) -> some View {
        var width = CGFloat.zero
        var height = CGFloat.zero

        return ZStack(alignment: .topLeading) {
            ForEach(self.platforms, id: \.self) { platform in
                self.item(for: platform)
                    .padding([.horizontal, .vertical], 4)
                    .alignmentGuide(.leading, computeValue: { d in
                        if (abs(width - d.width) > g.size.width)
                        {
                            width = 0
                            height -= d.height
                        }
                        let result = width
                        if platform == self.platforms.last! {
                            width = 0 //last item
                        } else {
                            width -= d.width
                        }
                        return result
                    })
                    .alignmentGuide(.top, computeValue: {d in
                        let result = height
                        if platform == self.platforms.last! {
                            height = 0 // last item
                        }
                        return result
                    })
            }
        }
    }

    func item(for text: String) -> some View {
        Text(text)
            .padding(.all, 5)
            .font(.body)
            .background(Color.blue)
            .foregroundColor(Color.white)
            .cornerRadius(5)
    }
}

struct TestWrappedLayout_Previews: PreviewProvider {
    static var previews: some View {
        TestWrappedLayout()
    }
}
于 2019-11-15T12:06:21.283 回答
6

对我来说,没有一个答案有效。要么是因为我有不同类型的元素,要么是因为周围的元素没有正确定位。因此,我最终实现了自己的 WrappingHStack,它的使用方式与 HStack 非常相似。你可以在GitHub 上找到它: WrappingHStack

这是一个例子:

在此处输入图像描述

代码:

WrappingHStack {
    Text("WrappingHStack")
        .padding()
        .font(.title)
        .border(Color.black)
    
    Text("can handle different element types")
    
    Image(systemName: "scribble")
        .font(.title)
        .frame(width: 200, height: 20)
        .background(Color.purple)
    
    Text("and loop")
        .bold()
    
    WrappingHStack(1...20, id:\.self) {
        Text("Item: \($0)")
            .padding(3)
            .background(Rectangle().stroke())
    }.frame(minWidth: 250)
}
.padding()
.border(Color.black)
于 2021-03-21T18:13:41.260 回答
4

我以前曾创造过你需要的东西。

HStackVStack.

您传入geometryProxy用于确定最大行宽的 a。我把它传入了,所以它可以在滚动视图中使用

我将 SwiftUI 视图包装在 UIHostingController 中,以获得每个孩子的大小。

然后,我循环浏览视图,将它们添加到行中,直到达到最大宽度,在这种情况下,我开始添加到新行。

这只是组合和输出 VStack 中的行的初始和最终阶段

struct WrappedHStack<Content: View>: View {
    
    private let content: [Content]
    private let spacing: CGFloat = 8
    private let geometry: GeometryProxy
    
    init(geometry: GeometryProxy, content: [Content]) {
        self.content = content
        self.geometry = geometry
    }
    
    var body: some View {
        let rowBuilder = RowBuilder(spacing: spacing,
                                    containerWidth: geometry.size.width)
        
        let rowViews = rowBuilder.generateRows(views: content)
        let finalView = ForEach(rowViews.indices) { rowViews[$0] }
        
        VStack(alignment: .center, spacing: 8) {
            finalView
        }.frame(width: geometry.size.width)
    }
}

extension WrappedHStack {
    
    init<Data, ID: Hashable>(geometry: GeometryProxy, @ViewBuilder content: () -> ForEach<Data, ID, Content>) {
        let views = content()
        self.geometry = geometry
        self.content = views.data.map(views.content)
    }

    init(geometry: GeometryProxy, content: () -> [Content]) {
        self.geometry = geometry
        self.content = content()
    }
}

魔法发生在这里

extension WrappedHStack {
    struct RowBuilder {
        
        private var spacing: CGFloat
        private var containerWidth: CGFloat
        
        init(spacing: CGFloat, containerWidth: CGFloat) {
            self.spacing = spacing
            self.containerWidth = containerWidth
        }
        
        func generateRows<Content: View>(views: [Content]) -> [AnyView] {
            
            var rows = [AnyView]()
            
            var currentRowViews = [AnyView]()
            var currentRowWidth: CGFloat = 0
            
            for (view) in views {
                let viewWidth = view.getSize().width
                
                if currentRowWidth + viewWidth > containerWidth {
                    rows.append(createRow(for: currentRowViews))
                    currentRowViews = []
                    currentRowWidth = 0
                }
                currentRowViews.append(view.erasedToAnyView())
                currentRowWidth += viewWidth + spacing
            }
            rows.append(createRow(for: currentRowViews))
            return rows
        }
        
        private func createRow(for views: [AnyView]) -> AnyView {
            HStack(alignment: .center, spacing: spacing) {
                ForEach(views.indices) { views[$0] }
            }
            .erasedToAnyView()
        }
    }
}

这是我使用的扩展

extension View {
    func erasedToAnyView() -> AnyView {
        AnyView(self)
    }
    
    func getSize() -> CGSize {
        UIHostingController(rootView: self).view.intrinsicContentSize
    }
}

您可以在此处查看带有一些示例的完整代码: https ://gist.github.com/kanesbetas/63e719cb96e644d31bf027194bf4ccdb

于 2021-03-13T22:30:56.367 回答
1

我遇到了同样的问题,为了解决它,我将对象项目传递给一个函数,该函数首先为项目创建视图,然后通过 UIHostController 我将根据项目宽度计算下一个位置。然后该函数返回项目视图。

import SwiftUI

class TestItem: Identifiable {
    
    var id = UUID()
    var str = ""
    init(str: String) {
        self.str = str
    }
    
}

struct AutoWrap: View {
    
    var tests: [TestItem] = [
        TestItem(str:"Ninetendo"),
        TestItem(str:"XBox"),
        TestItem(str:"PlayStation"),
        TestItem(str:"PlayStation 2"),
        TestItem(str:"PlayStation 3"),
        TestItem(str:"random"),
        TestItem(str:"PlayStation 4"),
    ]
    
    

    
    var body: some View {
        
        var curItemPos: CGPoint = CGPoint(x: 0, y: 0)
        var prevItemWidth: CGFloat = 0
        return GeometryReader { proxy in
            ZStack(alignment: .topLeading) {
                ForEach(tests) { t in
                    generateItem(t: t, curPos: &curItemPos, containerProxy: proxy, prevItemWidth: &prevItemWidth)
                }
            }.padding(5)
        }
    }
    
    func generateItem(t: TestItem, curPos: inout CGPoint, containerProxy: GeometryProxy, prevItemWidth: inout CGFloat, hSpacing: CGFloat = 5, vSpacing: CGFloat = 5) -> some View {
        let viewItem = Text(t.str).padding([.leading, .trailing], 15).background(Color.blue).cornerRadius(25)
        let itemWidth = UIHostingController(rootView: viewItem).view.intrinsicContentSize.width
        let itemHeight = UIHostingController(rootView: viewItem).view.intrinsicContentSize.height
        let newPosX = curPos.x + prevItemWidth + hSpacing
        let newPosX2 = newPosX + itemWidth
        if newPosX2 > containerProxy.size.width {
            curPos.x = hSpacing
            curPos.y += itemHeight + vSpacing
        } else {
            curPos.x = newPosX
        }
        prevItemWidth = itemWidth
        return viewItem.offset(x: curPos.x, y: curPos.y)
    }
}

struct AutoWrap_Previews: PreviewProvider {
    static var previews: some View {
        AutoWrap()
    }
}
于 2021-01-20T12:29:44.633 回答
0

我有类似这段代码的东西(相当长)。在简单的场景中它工作正常,但在几何阅读器的深度嵌套中它不能很好地传播它的大小。

如果这个视图像 Text() 扩展父视图内容一样包装和流动会很好,但它似乎已经明确地设置了它从父视图的高度。

https://gist.github.com/michzio/a0b23ee43a88cbc95f65277070167e29

这是代码中最重要的部分(没有预览和测试数据)

private func flow(in geometry: GeometryProxy) -> some View {
        
        print("Card geometry: \(geometry.size.width) \(geometry.size.height)")
        
        return ZStack(alignment: .topLeading) {
            //Color.clear
            ForEach(data, id: self.dataId) { element in
                self.content(element)
                    .geometryPreference(tag: element\[keyPath: self.dataId\])
                    /*
                    .alignmentGuide(.leading) { d in
                        print("Element: w: \(d.width), h: \(d.height)")
                        if (abs(width - d.width) > geometry.size.width)
                        {
                            width = 0
                            height -= d.height
                        }
                        
                        let result = width
                        
                        if element\[keyPath: self.dataId\] == self.data.last!\[keyPath: self.dataId\] {
                            width = 0 //last item
                        } else {
                            width -= d.width
                        }
                        return result
                    }
                    .alignmentGuide(.top) { d in
                        let result = height
                        if element\[keyPath: self.dataId\] == self.data.last!\[keyPath: self.dataId\] {
                            height = 0 // last item
                        }
                        return result
                    }*/
                    
                    .alignmentGuide(.top) { d in
                        self.alignmentGuides\[element\[keyPath: self.dataId\]\]?.y ?? 0
                    }
                    .alignmentGuide(.leading) { d in
                        self.alignmentGuides\[element\[keyPath: self.dataId\]\]?.x ?? 0
                    }
            }
        }
        .background(Color.pink)
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
        //.animation(self.loaded ? .linear(duration: 1) : nil)
        
        .onPreferenceChange(_GeometryPreferenceKey.self, perform: { preferences in
        
                DispatchQueue.main.async {
                    let (alignmentGuides, totalHeight) = self.calculateAlignmentGuides(preferences: preferences, geometry: geometry)
                    self.alignmentGuides = alignmentGuides
                    self.totalHeight = totalHeight
                    self.availableWidth = geometry.size.width
                }
        })
    }
    
    func calculateAlignmentGuides(preferences: \[_GeometryPreference\], geometry: GeometryProxy) -> (\[AnyHashable: CGPoint\], CGFloat) {
        
        var alignmentGuides = \[AnyHashable: CGPoint\]()
        
        var width: CGFloat = 0
        var height: CGFloat = 0
        
        var rowHeights: Set<CGFloat> = \[\]

        preferences.forEach { preference in
            let elementWidth = spacing + preference.rect.width
            
            if width + elementWidth >= geometry.size.width {
                width = 0
                height += (rowHeights.max() ?? 0) + spacing
                //rowHeights.removeAll()
            }
            
            let offset = CGPoint(x: 0 - width, y: 0 - height)
            
            print("Alignment guides offset: \(offset)")
            alignmentGuides\[preference.tag\] = offset
            
            width += elementWidth
            rowHeights.insert(preference.rect.height)
        }

        return (alignmentGuides, height + (rowHeights.max() ?? 0))
    }
}

图片

于 2020-04-03T17:02:39.363 回答
-3

您需要在文本视图之后立即处理行配置。如果您需要多行,请不要使用 lineLimit(1)。

 HStack(alignment: .top, spacing: 10){
                ForEach(collection.platforms, id: \.self) { platform in
                    Text(platform)
                    .fixedSize(horizontal: false, vertical: true)
                    .lineLimit(10)
                    .multilineTextAlignment(.leading)
                        .padding(.all, 5)
                        .font(.caption)
                        .background(Color.blue)
                        .foregroundColor(Color.white)
                        .cornerRadius(5)

                }
            }
于 2019-11-13T17:59:48.537 回答