What You Will Build
A custom tab bar with four tabs, each with a unique tint color. Icons bounce with .symbolEffect(.bounce) on selection and switch to filled variants. The bar uses .ultraThinMaterial for a frosted glass look.
Step 1: Tab Enum
private enum CustomTab: CaseIterable {
case home, search, favorites, profile
var title: String {
switch self {
case .home: "Home"
case .search: "Search"
case .favorites: "Favorites"
case .profile: "Profile"
}
}
var icon: String {
switch self {
case .home: "house"
case .search: "magnifyingglass"
case .favorites: "heart"
case .profile: "person"
}
}
var selectedIcon: String {
switch self {
case .home: "house.fill"
case .search: "magnifyingglass"
case .favorites: "heart.fill"
case .profile: "person.fill"
}
}
var tintColor: Color {
switch self {
case .home: .blue
case .search: .orange
case .favorites: .red
case .profile: .purple
}
}
}
Step 2: Build the Custom Bar
ZStack(alignment: .bottom) {
TabView(selection: $selectedTab) {
ForEach(CustomTab.allCases, id: \.self) { tab in
Text(tab.title).tag(tab)
}
}
HStack {
ForEach(CustomTab.allCases, id: \.self) { tab in
Spacer()
VStack(spacing: 4) {
Image(systemName: selectedTab == tab
? tab.selectedIcon : tab.icon)
.font(.title3)
.symbolEffect(.bounce, value: selectedTab == tab)
Text(tab.title).font(.caption2)
}
.foregroundStyle(selectedTab == tab ? tab.tintColor : .gray)
.onTapGesture {
withAnimation(.spring) { selectedTab = tab }
}
Spacer()
}
}
.padding(.vertical, 10)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 20))
.padding(.horizontal)
}
Tips
- .symbolEffect requires iOS 17. Use scaleEffect with spring for iOS 16.
- Hide default bar:
.toolbar(.hidden, for: .tabBar)on iOS 16+. - Safe area: Add bottom padding for the home indicator.
iOS Version: iOS 15+ base, iOS 17+ for symbolEffect