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:
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"]) } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With