Swiftui Hstack With Wrap and Dynamic Height

SwiftUI HStack with wrap and dynamic height

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

Sample Image

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

backup

Align heights of HStack members

Wrap your Image in a Text. Since your image is from SF Symbols, SwiftUI will scale it to match the dynamic type size. (I'm not sure how it will scale other images.)

VStack {
let background = RoundedRectangle(cornerRadius: 10)
.foregroundColor(.red)

ForEach(Font.TextStyle.allCases, id: \.self) { style in
HStack {
Text("\(style)" as String)
.padding()
.background(background)

Spacer()

Text(Image(systemName: "checkmark.seal.fill"))
.padding()
.background(background)
}
.font(.system(style))
}
}

screen shot showing all dynamic type sizes

Make a View the same size as another View which has a dynamic size in SwiftUI

As @Asperi pointed out, this solves the problem: stackoverflow.com/a/62451599/12299030

This is how I solved it in this case:

import SwiftUI

struct TestScreen: View {
@State private var imageHeight = CGFloat.zero

var body: some View {
VStack {
HStack {
Image("blue-test-image")
.resizable()
.scaledToFit()
.frame(maxWidth: .infinity)
.background(GeometryReader {
Color.clear.preference(
key: ViewHeightKeyTestScreen.self,
value: $0.frame(in: .local).size.height
)
})
VStack {
Text("Some Text")
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
Text("Other Text")
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
}
.frame(maxWidth: .infinity, minHeight: self.imageHeight, maxHeight: self.imageHeight)
}
.onPreferenceChange(ViewHeightKeyTestScreen.self) {
self.imageHeight = $0
}
Spacer()
}
.padding()
}
}

struct ViewHeightKeyTestScreen: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}

struct TestScreen_Previews: PreviewProvider {
static var previews: some View {
TestScreen()
}
}

Constrain the size of an HStack (with Images) to the width of the device

Use resizable() and aspectRatio()modifier for Image.

HStack {
Image("apple").resizable().aspectRatio(contentMode: .fit)
Image("cherry").resizable().aspectRatio(contentMode: .fit)
Image("star").resizable().aspectRatio(contentMode: .fit)
}

You can also use

Image("star").resizable().scaledToFit()

SwiftUI Text scaledToFit and wrap text

try this, and similarly for the pink:

    HStack {
Text(lorem)
.frame(width: 100, height: 100) // <--- here
.allowsTightening(true)
.lineLimit(2)
.scaledToFit()
.minimumScaleFactor(0.7)
}
.frame(width: 100, height: 100)
.background(Color.green)

How spacing is calculated in HStack

Building on top of @Asperi's comment: the spacing is applied in the same way it's applied to Text or Button standard views, which is between the frames of the views.

If you click on an element in the preview (this doesn't work when on live preview mode), you can see the frame of an element outlined in blue. The spacing is applied between the edges of the frames of each view.



Related Topics



Leave a reply



Submit