In SwiftUI, the TabView APIs and customization is somewhat limited. We can often see tutorials and articles on how to create custom tab view components replicating similar layouts and behaviors. This time I want to show you very simple explorations I did last night with the native TabView component that will add some sparks to your app.
The code is super simple, let's start with a basic TabView layout in SwiftUI within our main view's body.
var body: some View {
TabView(selection: $selectedTab) {
FirstScreen()
.tabItem {
Image(systemName: "1.circle.fill")
Text("First tab")
}
.tag(RootTab.first)
SecondScreen()
.tabItem {
Image(systemName: "2.circle.fill")
Text("Second tab")
}
.tag(RootTab.second)
ThirdScreen()
.tabItem {
Image(systemName: "3.circle.fill")
Text("Third tab")
}
.tag(RootTab.third)
}
.background(Color.secondary)
.tint(.red)
}
A couple of things here:
- We are using the initializer of
TabViewthat receives a Binding for the selected value. We will use it later also, but for now, we will declare a @State var in our host view:
@State private var selectedTab: RootTab = .second
- This is how it looks the RootTab enum, but you can basically use you own typed tabs.
internal enum RootTab: Int {
case first, second, third
/// We use this to color the light of each tab when selected.
var selectionColor: Color {
switch self {
case .first, .third:
return .purple
case .second:
return .main
}
}
}
Now, this is the standard look and feel of the TabView, which doesn't bring that much joy :(
The idea is to add a light that focuses on the selected tab, and changes when the user switches to other tabs. For that, let's use an overlay modifier in out TabView like follows:
TabView() {...}
.overlay(alignment: .bottom) {
let color = selectedTab.selectionColor
GeometryReader { geometry in
let aThird = geometry.size.width / 3
VStack {
Spacer()
Circle()
.background(color.blur(radius: 20))
.frame(width: aThird, height: 30)
.shadow(color: color, radius: 40)
.offset(
x: CGFloat(selectedTab.rawValue) * aThird,
y: 30
)
}
}
}
.edgesIgnoringSafeArea(.bottom)
How does it work?
- The overlay adds a
Circleshape on top of ourTabView. - We style the circle to have a shadow with big blur value that will simulate the light in the selected tab.
- The background modifier doesn't really need to be a blurred color but it enhanced the
lighteffect a bit. You can play around and see how it works for you. - We set the width of the
Circleto third of the screen's width by using aGeometryReaderto get the size of theTabView. The height is up to you but it changes the strength of the shadow so again, play around to get the best results. - Then we use an offset modifier to position the
Circleshape, this allow us to position the shape itself below theTabViewand letting only the shadow portion visible. - The horizontal offset(x axis), will be updated when the
selectedTabchanges, so the lightmoveswhen the user switches tabs. - Last, but no least, the
edgesIgnoringSafeAreaallows the overlay to ignore the safe area and go beyond the screen.
Then, we can add a simple Spring animation when the selectedTab value changes to give a better overall experience:
.animation(.spring(), value: selectedTab)
🎉 This is the final result:
It gets better on dark mode:
One thing to have in mind is that the overlay is on top of our TabView, as the name says, so we have to be careful not to cover our tab items if we add something more than a light.
That is it, simple and effective. I hope you enjoy the simplicity of SwiftUI like I do. 🚀


Oldest comments (0)