I was trying to change the tint color of an unselected item in SwiftUI.
Currently, I got it so it has a background and changes the tint color (making the tab bar visible in .tabItem gets rid of the .red tint and makes it gray)
import SwiftUI
import UIKit
struct Test: View{
init(){
UITabBar.appearance().backgroundColor = UIColor.systemBackground
UITabBar.appearance().unselectedItemTintColor = .red
}
var body: some View{
TabView{
Text("1")
.tabItem{
Image(systemName: "heart.fill")
Text("Heart")
}
Text("2")
.tabItem{
Image(systemName: "gearshape.fill")
Text("Settings")
}
}
}
}
#Preview{
Test()
}
Now I'm trying to add the divider line that separates the tab bar with the rest of the stuff on the page. Doing .toolbarBackground(.visible, for: .tabBar) brings back the divider, but then the unselected item tint doesn't work anymore.
TabView{
Text("1")
.tabItem{
Image(systemName: "heart.fill")
Text("Heart")
}
.toolbarBackground(.visible, for: .tabBar)
I also tried doing this method but it seems like it doesn't work anymore.
I would just abandon the native tab bar and use a custom one. Then you can style it any way you like.
LabelStyle
can be used to style the labels of the buttons.Something like:
enum TabType: CaseIterable {
case heart
case settings
var titleKey: String {
"\(self)".capitalized
}
var symbolName: String {
switch self {
case .heart: "heart"
case .settings: "gearshape"
}
}
}
struct TabLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
VStack(spacing: 4) {
configuration.icon
.dynamicTypeSize(.xxxLarge)
configuration.title
.font(.caption)
}
}
}
struct CustomTabBar: View {
@Binding var selection: TabType
var body: some View {
HStack {
ForEach(TabType.allCases, id: \.self) { type in
Button {
withAnimation(.spring) {
selection = type
}
} label: {
Label(type.titleKey, systemImage: type.symbolName)
.labelStyle(TabLabelStyle())
.symbolVariant(selection == type ? .fill : .none)
.foregroundStyle(selection == type ? Color.accentColor : .red)
.frame(maxWidth: .infinity)
}
}
}
.padding()
.background(.bar)
.overlay(alignment: .top) { Divider() }
}
}
The custom tab bar can be attached to the main content using .safeAreaInset(edge: .bottom)
.
TabView
if you like. This might have some advantages in terms of preserving state (such as, preserving scroll position). When doing it this way, the native tab bar must be hidden for every child view:struct ContentView: View {
@State private var selection = TabType.heart
var body: some View {
TabView(selection: $selection) {
Text("1")
.tag(TabType.heart)
.toolbarVisibility(.hidden, for: .tabBar)
// pre iOS 18: .toolbar(.hidden, for: .tabBar)
Text("2")
.tag(TabType.settings)
.toolbarVisibility(.hidden, for: .tabBar)
}
.safeAreaInset(edge: .bottom) {
CustomTabBar(selection: $selection)
}
}
}
ZStack
. This allows you to determine the transition when switching:ZStack {
switch selection {
case .heart:
Text("1")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(.move(edge: .leading))
case .settings:
Text("2")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(.move(edge: .trailing))
}
}
.safeAreaInset(edge: .bottom) {
CustomTabBar(selection: $selection)
}
If you want to use a sliding transition for three or more tabs, you might find the answer to SwiftUI bi-directional move transition moving the wrong way in certain cases useful.