
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.