How to make HStack fill its parent view's width (Scroll View)

216 Views Asked by At

I created a tabBar including 2 tabs. It has a horizontal scrollBar which includes a HStack view. But the HStack view only takes a part of screen width as screenshot in below. I used .frame(maxWidth: .infinity), however it changed nothing.

What I want is to make HStack view takes all the screen width size. Please see the 2nd screenshot in below.

struct ContentView: View {
    @State private var currentTab: Int = 0
    
    var body: some View {
        ZStack(alignment: .top) {
            TabView(selection: $currentTab) {
                TextClipView().tag(0)
                BookView().tag(1)
            }
            .tabViewStyle(.page(indexDisplayMode: .never))
            .ignoresSafeArea(.all)
            
            TabBarView(currentTab: $currentTab)
        }
    }
}

struct TabBarView: View {
    @Binding var currentTab: Int
    @Namespace var namespace
    
    var tabBarOptions: [String] = ["text.bubble", "book"]
    
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 20) {
                ForEach(Array(zip(tabBarOptions.indices, tabBarOptions)),
                        id: \.0,
                        content: {
                    index, name in
                    TabBarItem(currentTab: $currentTab, 
                               namespace: namespace,
                               tabBarItemName: name,
                               tab: index)
                })
            }
            .padding(.horizontal)
            .frame(maxWidth: .infinity)
            .border(.red, width: 4)
        }
        .frame(height: 80)
        .border(.green, width: 2)
        .ignoresSafeArea(.all)
    }
}

struct TabBarItem: View {
    @Binding var currentTab: Int
    let namespace: Namespace.ID
    
    var tabBarItemName: String
    var tab: Int
    
    var body: some View {
        Button {
            currentTab = tab
        } label: {
            VStack {
                Spacer()
                Image(systemName: tabBarItemName)
                    .font(.title)
                if currentTab == tab {
                    Color.black
                        .frame(height: 2)
                        .matchedGeometryEffect(id: "underline",
                                               in: namespace,
                                               properties: .frame)
                } else {
                    Color.clear.frame(height: 2)
                }
            }
            .animation(.spring(), value: currentTab)
        }
        .buttonStyle(.plain)
    }
}


#Preview {
    ContentView()
}

enter image description here enter image description here

2

There are 2 best solutions below

1
Benzy Neez On BEST ANSWER

Presumably, you are using a ScrollView because there might be times when the width of the content exceeds the available width.

To make the content fill the Scrollview when its ideal width is smaller, you can use a GeometryReader to measure the width and then set this as minWidth on the HStack.

Like this:

// TabBarView

var body: some View {
    GeometryReader { proxy in
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 20) {
                // content as before
            }
            .padding(.horizontal)
            .frame(minWidth: proxy.size.width)
            .border(.red, width: 4)
        }
    }
    .frame(height: 80)
    .border(.green, width: 2)
    .ignoresSafeArea(.all)
}
2
Saleh7 On
struct TabBarView: View {
    @Binding var currentTab: Int
    @Namespace var namespace
    
    var tabBarOptions: [String] = ["text.bubble", "book", "star", "heart", "message"]
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 20) {
                ForEach(Array(zip(tabBarOptions.indices, tabBarOptions)), id: \.0) { index, name in
                    TabBarItem(currentTab: $currentTab, namespace: namespace, tabBarItemName: name, tab: index)
                }
                Spacer() // Add a Spacer to push buttons to the leading edge when they don't fit
            }
            .padding(.horizontal)
            .frame(maxWidth: .infinity, alignment: .leading) // Allow HStack to expand only when needed
        }
        .frame(height: 80)
        .border(.green, width: 2)
        .ignoresSafeArea(.all)
    }
}

Spacer is added after the buttons in the HStack. This Spacer pushes the buttons to the leading edge when they don't fit within the available width. Additionally, we use .frame(maxWidth: .infinity, alignment: .leading) on the HStack to allow it to expand only when its natural width is smaller than the screen width. This way, it will display all the tabs when they fit, and if there are more tabs than can fit, it will allow horizontal scrolling within the ScrollView.

Try it :)