February 19, 2021

Using SwiftUI Environments Values to change Tabs programmatically.

In this post, we will explore the
Environment Key
and
EnvironmentValues
to achieve the programmatic tab switching in
TabView
.

Table of Contents

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.

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

Stay Ahead with Our Monthly Newsletter!

Get the latest tech insights, trends, and updates delivered straight to your inbox!

Featured Insights

Team up with us to enhance and

achieve your business objectives

LET'S WORK

TLogoGETHER