Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI HStack with wrap and dynamic height

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 below. The tags are shown properly but the height of the view itself is not calculated inside the VStack. How can I make this view use the height of is content?

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

Example code:

struct ExampleTagsView: View {     var body: some View {         ScrollView {             VStack(alignment: .leading) {                 Text("Platforms:")                 TestWrappedLayout()                  Text("Other Platforms:")                 TestWrappedLayout()             }         }     } }  struct ExampleTagsView_Previews: PreviewProvider {     static var previews: some View {         ExampleTagsView()     } } 

Result: enter image description here

like image 667
Ludyem Avatar asked May 30 '20 12:05

Ludyem


1 Answers

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

enter image description here

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"])         }     } } 
like image 188
Asperi Avatar answered Sep 22 '22 23:09

Asperi