Handle TabView data in a type-safe way with Enums
Enumerations, or enums for short, are a data type that allow you to define your own values and work with them in a type-safe way. This greatly reduces the likelihood of unexpected and invalid states and makes your code more readable.
In this article, we'll create a TabView
and utilize enums to define the data we need, in a type-safe way.
Creating a TabView
Let's create a new view called TabViewNavigation with a TabView
and two tab items:
struct TabNavigationView: View {
// Persist the selected tab between app launches
@SceneStorage("SelectedTab") private var selectedTab = "Home"
var body: some View {
TabView {
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}
.tag("Home")
SettingsView()
.tabItem {
Label("Settings", systemImage: "gear")
}
.tag("Settings")
}
}
}
We create two tabs called Home and Settings with appropriate SFSymbols. Tabs have unique names, so we use their name as a tag.
This implementation works great. You can create a TabView
like this and use it in your app without any issues. However, notice we're using raw strings in our labels and "Home" string is repeated three times.
While this works, there's a better way to handle all the tab information in a type-safe way without repeating strings and calling for potential errors in your code.
Creating a Tab enum
This is a perfect example of where to use enums in your code. Each tab in the TabView
is a unique value. There are no two tabs with the same name.
Create a new enum
called Tab with two cases:
enum Tab {
case home
case settings
}
The number of values depends on how many tabs you have.
Handle tab data
I want to expand the enum to handle all data for the tab's Label
. Add two computed properties; title and icon:
// Set `String` as raw value type for @SceneStorage
enum Tab: String {
case home
case settings
// tab title
var title: String {
switch self {
case .home:
return "Home"
case .settings:
return "Settings"
}
}
// tab icon
var icon: String {
switch self {
case .home:
return "house"
case .settings:
return "gear"
}
}
}
Here, we use switch on the enum itself and return the string depending on the value.
Both properties are of type String
because that's what the Label
initializer requires.
Apart from title and icon, we want to use enum to persist the selected tab with @SceneStorage
. You need to specify String
as the raw type for the enum so you can use it with @SceneStorage
.
Using Tab with TabView
Now all that's left is to rewrite the TabNavigationView and use Tab
instead:
struct TabNavigationView: View {
@SceneStorage("SelectedTab") private var selectedTab: Tab = .home
var body: some View {
TabView {
HomeView()
.tabItem {
Label(Tab.home.title, systemImage: Tab.home.icon)
}
.tag(Tab.home)
SettingsView()
.tabItem {
Label(Tab.settings.title, systemImage: Tab.settings.icon)
}
.tag(Tab.settings)
}
}
}
That looks better than using raw strings everywhere. It's more readable and it's type-safe as well.
Final Thoughts
Enums are a first-class types in Swift, and unlike in some other programming langages, can be very powerful behind the basic implementation. They can be extended to add additional functionalities as well.
Utilizing enums in your code can help you make it more readable and avoid unnecessary errors.
If you want to learn more about enums, the official Swift documentation is a great place to start.
Please feel free to reach out on X (Twitter) or Mastodon if you have any questions, comments, or feedback.
Thank you for reading and happy coding!