In SwiftUI, achieving a horizontally stacked layout with wrapping behavior (similar to UIStackView
with UIStackViewDistribution.fillEqually
and UIStackViewAxis.horizontal
in UIKit) along with dynamic height can be a bit tricky because SwiftUI’s built-in layout system doesn’t directly support wrapping behavior. However, you can achieve a similar effect using a combination of GeometryReader
, PreferenceKey
, and custom view layout.
Here’s a basic implementation of an HStack
with wrapping behavior and dynamic height:
import SwiftUI
struct ContentView: View {
let items = (1...20).map { "Item \($0)" } // Sample items
var body: some View {
VStack(alignment: .leading) {
WrappedHStack(items: items, maxWidth: UIScreen.main.bounds.width) // Adjust maxWidth as needed
.padding(.horizontal)
Spacer()
}
.padding()
}
}
struct WrappedHStack: View {
let items: [String]
let maxWidth: CGFloat
var body: some View {
GeometryReader { geometry in
self.generateContent(in: geometry)
}
}
private func generateContent(in geometry: GeometryProxy) -> some View {
var width: CGFloat = 0
var height: CGFloat = 0
return ZStack(alignment: .topLeading) {
ForEach(items, id: \.self) { item in
Text(item)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(4)
.alignmentGuide(.leading, computeValue: { d in
if (abs(width - d.width) > maxWidth) {
width = 0
height -= d.height
}
let result = width
if item == self.items.last! {
width = 0 //last item
} else {
width -= d.width
}
return result
})
.alignmentGuide(.top, computeValue: {d in
let result = height
if item == self.items.last! {
height = 0 //last item
}
return result
})
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In this example:
-
ContentView
contains the main view hierarchy. It includes aVStack
with aWrappedHStack
. -
WrappedHStack
is a custom view that takes an array of items and a maximum width. It usesGeometryReader
to measure available space and lays out the items accordingly. It calculates the width and height of each item, and if the width exceeds the available space, it wraps the items onto the next line. - Each item is represented as a
Text
view with some styling applied.
This implementation provides a horizontally wrapped layout with dynamic height based on the content. Adjust the padding, spacing, and styling to match your requirements.
2. Using a Third-Party Library (Optional):
Libraries like WrappingHStack
(https://github.com/dkk/WrappingHStack) offer a pre-built solution with similar functionality. These libraries often provide additional features or customization options.
Usage example (assuming you downloaded the library):
import WrappingHStack
struct MyView: View {
let items = ["Item 1", "This is a longer item", "Item 3"]
var body: some View {
WrappingHStack(models: items) { text in
Text(text)
.padding(10)
.background(Color.gray.opacity(0.2))
}
}
}
Choosing the right method:
- If you need more control over the layout or prefer a lightweight solution, consider implementing the custom approach.
- If you want a quicker solution and don’t mind adding a dependency, using a third-party library can be convenient.
Both methods achieve an HStack with wrapping and dynamic height in SwiftUI. Select the approach that best suits your project’s requirements and preferences.