SwiftUI HStack with wrap and dynamic height

后端 未结 2 1878
别那么骄傲
别那么骄傲 2020-11-30 12:01

I have this view to show text tags on multiple lines which I got from SwiftUI HStack with Wrap, but when I add it in a VStack the tags overlap any other view that I put belo

相关标签:
2条回答
  • 2020-11-30 12:26

    I just managed to solve this by moving the GeometryReader up to the ExampleTagsView and using platforms.first instead of last inside .alignmentGuide

    Full code:

    import SwiftUI
    
    struct ExampleTagsView: View {
        var body: some View {
            GeometryReader { geometry in
                ScrollView(.vertical) {
                    VStack(alignment: .leading) {
                        Text("Platforms:")
                        TestWrappedLayout(geometry: geometry)
    
                        Text("Other Platforms:")
                        TestWrappedLayout(geometry: geometry)
                    }
                }
            }
        }
    }
    
    struct ExampleTagsView_Previews: PreviewProvider {
        static var previews: some View {
            ExampleTagsView()
        }
    }
    
    struct TestWrappedLayout: View {
        @State var platforms = ["Ninetendo", "XBox", "PlayStation", "PlayStation 2", "PlayStation 3", "PlayStation 4", "PlayStation 5", "Ni", "Xct5Box", "PlayStatavtion", "PlvayStation 2", "PlayStatiadfon 3", "PlaySdatation 4", "PlaySdtation 5"]
        let geometry: GeometryProxy
    
        var body: some View {
            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.first! {
                                width = 0 //last item
                            } else {
                                width -= d.width
                            }
                            return result
                        })
                        .alignmentGuide(.top, computeValue: {d in
                            let result = height
                            if platform == self.platforms.first! {
                                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)
        }
    }
    
    

    Result:

    0 讨论(0)
  • 2020-11-30 12:40

    Ok, here is a bit more generic & improved variant (for the solution initially introduced in SwiftUI HStack with Wrap)

    Tested with Xcode 11.4 / iOS 13.4

    Note: as height of view is calculated dynamically the result works in run-time, not in Preview

    struct TagCloudView: View {
        var tags: [String]
    
        @State private var totalHeight 
              = CGFloat.zero       // << variant for ScrollView/List
        //    = CGFloat.infinity   // << variant for VStack
    
        var body: some View {
            VStack {
                GeometryReader { geometry in
                    self.generateContent(in: geometry)
                }
            }
            .frame(height: totalHeight)// << variant for ScrollView/List
            //.frame(maxHeight: totalHeight) // << variant for VStack
        }
    
        private func generateContent(in g: GeometryProxy) -> some View {
            var width = CGFloat.zero
            var height = CGFloat.zero
    
            return ZStack(alignment: .topLeading) {
                ForEach(self.tags, id: \.self) { tag in
                    self.item(for: tag)
                        .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 tag == self.tags.last! {
                                width = 0 //last item
                            } else {
                                width -= d.width
                            }
                            return result
                        })
                        .alignmentGuide(.top, computeValue: {d in
                            let result = height
                            if tag == self.tags.last! {
                                height = 0 // last item
                            }
                            return result
                        })
                }
            }.background(viewHeightReader($totalHeight))
        }
    
        private func item(for text: String) -> some View {
            Text(text)
                .padding(.all, 5)
                .font(.body)
                .background(Color.blue)
                .foregroundColor(Color.white)
                .cornerRadius(5)
        }
    
        private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
            return GeometryReader { geometry -> Color in
                let rect = geometry.frame(in: .local)
                DispatchQueue.main.async {
                    binding.wrappedValue = rect.size.height
                }
                return .clear
            }
        }
    }
    
    struct TestTagCloudView : View {
        var body: some View {
            VStack {
                Text("Header").font(.largeTitle)
                TagCloudView(tags: ["Ninetendo", "XBox", "PlayStation", "PlayStation 2", "PlayStation 3", "PlayStation 4"])
                Text("Some other text")
                Divider()
                Text("Some other cloud")
                TagCloudView(tags: ["Apple", "Google", "Amazon", "Microsoft", "Oracle", "Facebook"])
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题