SwiftUI: how to create a Tab View
By Flavio Copes
Learn how to create a tab bar in SwiftUI with TabView and the Tab API, giving each screen a title and SF Symbol so users can switch by tapping an icon.
To create a tab view in SwiftUI you wrap your screens in a TabView, and you declare each tab with the Tab API (title, SF Symbol, and content).
It’s common in iOS apps to use a Tab View. The one with a few choices at the bottom, and you can completely switch what’s in the screen by tapping the icon / label.
Here’s the simplest possible example of a TabView on iOS 18 and later:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
Tab("First", systemImage: "tray") {
Text("First")
}
Tab("Second", systemImage: "calendar") {
Text("Second")
}
}
}
}
And here’s the result. The screenshot is from 2021 and the code in it uses the older .tabItem syntax I show further down, but the tab bar it produces is the same:

See? We have a TabView view, and inside it, we have 2 tabs.
Both show a Text view to make it simple.
Tab("Title", systemImage:) adds that screen to the tab bar with a label and an icon. The systemImage parameter takes the name of an SF Symbol, Apple’s built-in icon set. You can browse all the available names in the free SF Symbols app.
Of course you will want to use a custom view instead of Text in most cases.
Tab needs iOS 18. If your deployment target is older, you add the .tabItem() modifier to each child of TabView instead, with a Label inside. This is what the post showed when I wrote it, and what you see in the screenshot above:
TabView {
Text("First")
.tabItem {
Label("First", systemImage: "tray")
}
Text("Second")
.tabItem {
Label("Second", systemImage: "calendar")
}
}
It still works on iOS 18 and later, but for a new project use Tab.
How to switch tabs from code
Sometimes tapping is not enough. Maybe a button on the first screen should send the user to the second one.
To do that, TabView accepts a selection binding. You store the current tab in a @State property, and you pass a value on each Tab:
struct ContentView: View {
@State private var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
Tab("First", systemImage: "tray", value: 0) {
Text("First")
}
Tab("Second", systemImage: "calendar", value: 1) {
Text("Second")
}
}
}
}
Now setting selectedTab = 1 anywhere in your code switches to the second tab. When the user taps a tab, the binding updates too, so you always know which tab is on screen.
One nice thing about Tab: you can’t forget the values. If the TabView has a selection binding and a Tab has no value, the code doesn’t compile. The values must also have the same type as the state property, an Int here. With the older .tabItem syntax the equivalent is a .tag() modifier on each child, and there a missing tag is only a runtime problem: taps still work, but setting the state from code won’t move to the tab you expect.
Want me to talk about your product? You can sponsor this site.
Related posts about swift: