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
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))
}
}
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
Protocol Doesn't Conform to Itself
Alternative to Performselector in Swift
Swift: Print() VS Println() VS Nslog()
All Dates Between Two Date Objects (Swift)
How to Apply the Type to a Nsfetchrequest Instance
Ios 14 Swiftui Keyboard Lifts View Automatically
How to Get Text to Wrap in a Uilabel (Via Uiviewrepresentable) Without Having a Fixed Width
Swift Generics Not Preserving Type
What Is Difference Between Optional and Decodeifpresent When Using Decodable For Json Parsing
Using a Dispatch_Once Singleton Model in Swift
Nsobject Subclass in Swift: Hash VS Hashvalue, Isequal VS ==
What Does It Mean That String and Character Comparisons in Swift Are Not Locale-Sensitive
Check If 'Any' Value Is Object