Using SwiftUI Environments Values to change Tabs programmatically. in 2026

Published At: February 19, 2021

Using SwiftUI Environments Values to change Tabs programmatically. in 2026

Updated: April 23, 2026

In this post, we will explore the
Environment Key
and
EnvironmentValues
to achieve the programmatic tab switching in
TabView
.
Lets start with the empty SwiftUI template from the Xcode and Create new SwiftUI view named AppTabView.swift.

Define AppTabView

Here we define our tabs in AppTabView like below.
struct AppTabView: View { enum Tab { case profile case bookmarks case settings } @State private var selectedTab: Tab = .profile var body: some View { TabView(selection: $selectedTab) { ProfileView() .tabItem { Image(systemName: "person") Text("Profile") } .tag(Tab.profile) BookmarksView() .tabItem { Image(systemName: "book") Text("Bookmarks") } .tag(Tab.bookmarks) SettingsView() .tabItem { Image(systemName: "gear") Text("Settings") } .tag(Tab.settings) } } }
Here you can see we have used the
.tag
modifier in order to TabView to switch the current tab.
If we use @SceneStorage("selectedTab") instead of @State, then we can get some level of state-restoration behaviour.
Now below is the contents of the
ProfileView
,
BookmarksView
and
SettingsView
.
struct ProfileView: View { var body: some View { NavigationView { List { Button("Bookmarks") { print("Switch to Bookmarks Tab") } Button("Settings") { print("Switch to Settings Tab") } } .listStyle(InsetGroupedListStyle()) .navigationTitle("Profile") .navigationBarTitleDisplayMode(.inline) } } }
struct BookmarksView: View { var body: some View { NavigationView { List { NavigationLink(destination: BookmarkDetailView()) { Text("ICE") } Button("Settings") { print("Switch to Settings Tab") } } .listStyle(InsetGroupedListStyle()) .navigationTitle("Bookmarks") .navigationBarTitleDisplayMode(.inline) } } }
struct SettingsView: View { var body: some View { NavigationView { List { Button("Profile") { print("Switch to Profile View") } Button("Bookmarks") { print("Switch to Bookmarsk View") } } .listStyle(InsetGroupedListStyle()) .navigationTitle("Settings") .navigationBarTitleDisplayMode(.inline) } } }
Now we have the our basic tabs setup, now we need some way to pass the button press action to super view (
AppTabView
in our case ). So we can use the
EnvironmentKey
and
EnvironmentValues
here.
So let's start defining our EnviromentKey named
CurrentTabKey
like below.
struct CurrentTabKey: EnvironmentKey { static var defaultValue: AppTabView.Tab = .bookmarks }
To use above
EnvironmentKey
we need to extend the
EnvironmentValues
like below.
extension EnvironmentValues { var currentTab: AppTabView.Tab { get { self[CurrentTabKey.self] } set { self[CurrentTabKey.self] = newValue } } }
Now we can use this
CurrentTabKey
like below.
... TabView(selection: $selectedTab) { ProfileView() .tabItem { Image(systemName: "person") Text("Profile") } .tag(Tab.profile) .environment(\.currentTab, selectedTab) ...
And in the
ProfileView
we can use the environment property wrapper like below.
struct ProfileView: View { @Environment(\.currentTab) var tab var body: some View { NavigationView { List { Button("Bookmarks") { tab = .bookmarks } Button("Settings") { tab = .settings } } .listStyle(InsetGroupedListStyle()) .navigationTitle("Profile") .navigationBarTitleDisplayMode(.inline) } } }
Here we can use the environment variable in our button action. But...
Cannot assign to property: 'tab' is a get-only property
To solve this, we need to recall the two way data communication in SwiftUI. For this SwiftUI has property wrapper called
Binding
.

Set CurrentTabKey

So we update our EnvironmentKey struct CurrentTabKey like below.
struct CurrentTabKey: EnvironmentKey { static var defaultValue: Binding<AppTabView.Tab> = .constant(.bookmarks) }
And EnvironmentValues like below.
extension EnvironmentValues { var currentTab: Binding<AppTabView.Tab> { get { self[CurrentTabKey.self] } set { self[CurrentTabKey.self] = newValue } } }
Cannot convert value 'selectedTab' of type 'AppTabView.Tab' to expected type 'Binding<AppTabView.Tab>', use wrapper instead
Insert $
Fixing the above compiler error by putting the
$
in the environment modifier like below.
... ProfileView() .tabItem { Image(systemName: "person") Text("Profile") } .tag(Tab.profile) .environment(\.currentTab, $selectedTab) ...
And now finaly updating our Views like below.
... Button("Bookmarks") { tab.wrappedValue = .bookmarks } Button("Settings") { tab.wrappedValue = .settings } ...

Set BookmarkDetailView

struct BookmarkDetailView: View { @Environment(\.currentTab) var tab var body: some View { List { Button("Profile") { tab.wrappedValue = .profile } } .listStyle(InsetGroupedListStyle()) .navigationTitle("Bookmark Detail") .navigationBarTitleDisplayMode(.inline) } }
And here we have it.We hope you liked it. Thanks for reading.
Ratnesh Jain

Written by

Found this blog useful? Don't forget to share it wih your network

X (Twitter)

Featured Insights

Team up with us to enhance and

achieve your business objectives

LET'S WORK

TLogoGETHER