SwiftUI HStack with Wrap

后端 未结 4 584
孤街浪徒
孤街浪徒 2020-11-29 07:08

Is it possible that the blue tags (which are currently truncated) are displayed completely and then it automatically makes a line break?

NavigationLink(desti         


        
相关标签:
4条回答
  • 2020-11-29 07:35

    I have something like this code (rather long). In simple scenarios it works ok, but in deep nesting with geometry readers it doesn't propagate its size well.

    It would be nice if this views wraps and flows like Text() extending parent view content, but it seems to have explicitly set its height from parent view.

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

    Here is the most important part of the code (without preview and test data)

    [![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))
        }
    }][1]][1]
    
    0 讨论(0)
  • 2020-11-29 07:37

    Here is some approach of how this could be done using alignmentGuide(s). It is simplified to avoid many code post, but hope it is useful.

    Update: There is also updated & improved variant of below solution in my answer for SwiftUI HStack with wrap and dynamic height

    This is the result:

    swiftui wrapped layout

    And here is full demo code (orientation is supported automatically):

    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()
        }
    }
    
    0 讨论(0)
  • 2020-11-29 07:37

    You need to handle line configurations right after Text View. Don't use lineLimit(1) if you need multiple lines.

     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)
    
                    }
                }
    
    0 讨论(0)
  • 2020-11-29 07:42

    I found a Framework-Solution called ASCollectionView. I hope this helps someone who has the same problem.

    0 讨论(0)
提交回复
热议问题