SwiftUI HStack fill whole width with equal spacing

IosSwiftSwiftui

Ios Problem Overview


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

Ios Solutions


Solution 1 - Ios

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

Solution 2 - Ios

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

Solution 3 - Ios

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)

Solution 4 - Ios

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

Solution 5 - Ios

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

Solution 6 - Ios

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()
    }
  }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTomView Question on Stackoverflow
Solution 1 - IossvarrallView Answer on Stackoverflow
Solution 2 - IosJohn M.View Answer on Stackoverflow
Solution 3 - IoskgaidisView Answer on Stackoverflow
Solution 4 - IosMojtaba HosseiniView Answer on Stackoverflow
Solution 5 - IosWahab Khan JadonView Answer on Stackoverflow
Solution 6 - Iosal00pView Answer on Stackoverflow