带包装的SwiftUI HStack

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

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

带包装的SwiftUI HStack

此外,蓝色标记中不应包含换行符:

带包装的SwiftUI HStack

这应该是最后的样子:

带包装的SwiftUI HStack

a72137110 回答:带包装的SwiftUI HStack

我曾经创建过你需要的东西。

我在 HStack 中使用过 VStack

您传入用于确定最大行宽的 geometryProxy。 我把它传递进来,这样它就可以在滚动视图中使用

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

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

这只是 init 和 final 阶段组合和输出 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

,

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

这是结果:

swiftui wrapped layout

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

stateless allocator
,

我遇到了同样的问题,为了解决它,我将对象项传递给一个函数,该函数首先为项创建视图,然后通过 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()
    }
}
,

您需要在“文本视图”之后立即处理行配置。如果您需要多行,请不要使用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)

                }
            }
,

我找到了一个名为ASCollectionView的框架解决方案。希望这对遇到同样问题的人有所帮助。

,

我有类似这样的代码(相当长)。在简单的情况下,它可以正常工作,但是在与几何读取器进行深度嵌套时,它的尺寸不能很好地传播。

如果此视图像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))
    }
}

image

,

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

本文链接:https://www.f2er.com/3108027.html

大家都在问