SwiftUI HStack fill whole width with equal spacing

I have an HStack:

struct BottomList: View {
var body: some View {
HStack() {
ForEach(navData) { item in
NavItem(image: item.icon, title: item.title)
}
}
}
}

How do I perfectly center its content with equal spacing automatically filling the whole width?

FYI just like Bootstraps CSS class .justify-content-around

45103 次浏览

The various *Stack types will try to shrink to the smallest size possible to contain their child views. If the child view has an ideal size, then the *Stack will not expand to fill the screen. This can be overcome by placing each child on top of a clear Rectangle in a ZStack, because a Shape will expand as much as possible. A convenient way to do this is via an extension on View:

extension View {
func inExpandingRectangle() -> some View {
ZStack {
Rectangle()
.fill(Color.clear)
self
}
}
}

You can then call it like this:

struct ContentView: View {
var data = ["View", "View", "View"]


var body: some View {
VStack {


// This will be as small as possible to fit the items
HStack {
ForEach(data, id: \.self) { item in
Text(item)
.border(Color.red)
}
}


// Each item's invisible Rectangle forces it to expand
// The .fixedSize modifier prevents expansion in the vertical direction
HStack {
ForEach(data, id: \.self) { item in
Text(item)
.inExpandingRectangle()
.fixedSize(horizontal: false, vertical: true)
.border(Color.red)
}
}


}
}
}

You can adjust the spacing on the HStack as desired.

Non-expanding and expanding stacks

The frame layout modifier, with .infinity for the maxWidth parameter can be used to achieve this, without the need for an additional Shape View.

struct ContentView: View {
var data = ["View", "V", "View Long"]


var body: some View {
VStack {


// This will be as small as possible to fit the data
HStack {
ForEach(data, id: \.self) { item in
Text(item)
.border(Color.red)
}
}


// The frame modifier allows the view to expand horizontally
HStack {
ForEach(data, id: \.self) { item in
Text(item)
.frame(maxWidth: .infinity)
.border(Color.red)
}
}
}
}
}

Comparison using .frame modifier

If items are fullwidth compatible, it will be done automatically, you can wrap items between spacers to make it happen:

struct Resizable: View {
let text: String


var body: some View {
HStack {
Spacer()
Text(text)
Spacer()
}
}
}

SingleView

So you. can use it in a loop like:

HStack {
ForEach(data, id: \.self) { item in
Resizable(text: item)
}
}

MultiView

I inserted Spacer() after each item...but for the LAST item, do NOT add a Spacer():

struct BottomList: View {
var body: some View {
HStack() {
ForEach(data) { item in
Item(title: item.title)
if item != data.last { // match everything but the last
Spacer()
}
}
}
}
}

Example list that is evenly spaced out even when item widths are different: enter image description here

(Note: The accepted answers .frame(maxWidth: .infinity) did not work for all cases: it did not work for me when it came to items that have different widths)

You can also use spacing in stacks ... ie

HStack(spacing: 30){
            

Image("NetflixLogo")
.resizable()
.scaledToFit()
.frame(width: 40)
            

Text("TV Show")
Text("Movies")
Text("My List")
            

}
.frame(maxWidth: .infinity)

output result looks like this ...

enter image description here

If your array has repeating values, use array.indices to omit a spacer after the last element.

HStack() {
ForEach(data.indices) { i in
Text("\(data[i])")
if i != data.last {
Spacer()
}
}
}