Search code examples
iosswiftswiftuiswiftui-tabview

SwiftUI - Background color for view of tab item


Whenever I click on a tab on tabview, it opens a view. I wish to change the background color of that view. Doesn't matter what I do, it always stays white. Please look at the code and screenshot below:

Note: In the screenshot below, ignore positioning of the tab bar. In my actual app it shows at the bottom, but due to lot of code, I just created a sample app.

How can I set the background to gray and how can I remove the line/separator highlighted in the screenshot?

Code:

import SwiftUI
import Foundation

struct ContentView: View {
    static let sliderHandleHeight: CGFloat = 15.0
    static let radius: CGFloat = 16
    @State private var tabViewPanelMinHeight: CGFloat = 250.0
    @State private var tabViewPanelMaxHeight: CGFloat = 750.0
    @State private var tabViewPanelCurrentHeight: CGFloat = 250.0
    
    var body: some View {
        GeometryReader { geometry in
            VStack {
                Image(systemName: "touchid").resizable().scaledToFit()
            }
            .onAppear {
                tabViewPanelMinHeight = geometry.size.height * 0.25
                tabViewPanelMaxHeight = geometry.size.height * 0.91
                tabViewPanelCurrentHeight = tabViewPanelMinHeight
            }
            .overlay(alignment: .bottom) {
                VStack {
                    RoundedRectangle(cornerRadius: 5.0)
                        .foregroundColor(.secondary)
                        .frame(width: 50, height: 7.0)
                        .frame(height: ContentView.sliderHandleHeight)
                        .padding(.top, 5.0)
                        .simultaneousGesture(tap)
                    BottomTabBarView()
                }
                .background(Color(red: 0.98, green: 0.98, blue: 0.98).opacity(0.94))
                .cornerRadius(ContentView.radius)
                .shadow(radius: 10)
                .frame(width: geometry.size.width, height: tabViewPanelCurrentHeight + ContentView.sliderHandleHeight, alignment: .bottom)
            }
        }
    }
    
    var tap: some Gesture {
        TapGesture(count: 1).onEnded {
            tabViewPanelCurrentHeight = ((tabViewPanelCurrentHeight == tabViewPanelMinHeight) ? tabViewPanelMaxHeight : tabViewPanelMinHeight)
        }
    }
}

enum Tab: String, CaseIterable {
    case none
    case profile
    case search
}

struct BottomTabBarView: View {
    @State var activeTab = Tab.search

    var body: some View {
        TabView(selection: $activeTab) {
            Group {
                Text("Profile")
                    .tabItem {
                        Label("Profile", systemImage: "square.text.square.fill")
                    }
                    .tag(Tab.profile)
                SearchExploreModalView()
                    .tabItem {
                        Label("Search", systemImage: "magnifyingglass")
                        Text("Search")
                    }
                    .tag(Tab.search)
            }
        }
        .background(Color(red: 0.98, green: 0.98, blue: 0.98).opacity(0.94))
        .shadow(color: .black.opacity(0.3), radius: 0, x: 0, y: -0.5)
        .onAppear {
            let appearance = UITabBarAppearance()
            UITabBar.appearance().scrollEdgeAppearance = appearance
        }
    }
}

struct SearchExploreModalView: View {
    var body: some View {
        VStack {
            Text("Hello World")
        }
        .background(Color.gray)
    }
}

Screenshot:

enter image description here


Solution

  • To make the grey color the entire background of the tab, just allow the VStack containing the Text expand infinitely:

    struct SearchExploreModalView: View {
        var body: some View {
            VStack {
                Text("Hello World")
            }
            // here:
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.gray)
        }
    }
    

    The "line" that you see is part of the shadow you added to TabView:

    .shadow(color: .black.opacity(0.3), radius: 0, x: 0, y: -0.5)
    

    Removing this line removes the line.

    If you actually want the background of the tab to continue beyond the "grabber", use a ZStack instead of VStack:

    ZStack(alignment: .top) {
        BottomTabBarView()
    
        RoundedRectangle(cornerRadius: 5.0)
            .foregroundColor(.secondary)
            .frame(width: 50, height: 7)
            .frame(height: ContentView.sliderHandleHeight)
            .padding(.top, 5.0)
            .simultaneousGesture(tap)
    }
    .background(Color(red: 0.98, green: 0.98, blue: 0.98).opacity(0.94))
    .cornerRadius(ContentView.radius)
    .shadow(radius: 10)
    .frame(width: geometry.size.width, height: tabViewPanelCurrentHeight + ContentView.sliderHandleHeight, alignment: .bottom)
    

    Output:

    enter image description here

    This might cause the "grabber" to overlap with some of the tabs' contents. To avoid this, you can add a Spacer with height ContentView.sliderHandleHeight at the start of each tab.

    That said, are you trying to reinvent sheet?

    .sheet(isPresented: .constant(true)) {
        BottomTabBarView()
            .presentationDetents([.medium, .large])
    }