How do I use TabbedView in SwiftUI?
For those still searching for how to do this, here's an update with Xcode 11 GM.
struct Tabs: View {
@State private var selected = 0
var body: some View {
TabView(selection: $selected) {
MyFirstView()
.tabItem {
Image(systemName: (selected == 0 ? "star.fill" : "star"))
Text("One")
}.tag(0)
MySecondView()
.tabItem {
Image(systemName: (selected == 1 ? "star.fill" : "star"))
Text("Two")
}.tag(1)
MyThirdView()
.tabItem {
Image(systemName: (selected == 2 ? "star.fill" : "star"))
Text("Three")
}.tag(2)
}
.edgesIgnoringSafeArea(.all) // Important if you want NavigationViews to go under the status bar...
}
}
With XCode beta 3 the following should work:
import SwiftUI
struct Home : View {
@State private var currentTab = 1
var body: some View {
TabbedView(selection: $currentTab) {
FirstView()
.tabItem {
VStack {
Image(systemName: "1.circle")
Text("First Tab")
}
}.tag(1)
SecondView()
.tabItem {
VStack {
Image(systemName: "2.circle")
Text("Second Tab")
}
}.tag(2)
}
}
}
Enclosing the tab label in a VStack
seems to be optional, though. So, you might decide to drop this, like:
import SwiftUI
struct Home : View {
@State private var currentTab = 1
var body: some View {
TabbedView(selection: $currentTab) {
FirstView()
.tabItem {
Image(systemName: "1.circle")
Text("First Tab")
}.tag(1)
SecondView()
.tabItem {
Image(systemName: "2.circle")
Text("Second Tab")
}.tag(2)
}
}
}
TabbedView()
has been deprecated use TabView()
instead.
Using integers to select views smells bad to me, from my days working with tag()
of UIButton and UIView, it is better to enumerate what you are doing rather than assign a hard coded values that have a very large range. i.e. Int.min()
to Int.max()
. This also makes code easier to read and maintain in the future.
TabView(selection: )
can be used to select the index, and is declared as:
public struct TabView<SelectionValue, Content> : View where SelectionValue : Hashable, Content : View {
public init(selection: Binding<SelectionValue>?, @ViewBuilder content: () -> Content)
…
This means that you can select the index with any hashable content.
We can use a enum
that conforms to Hashable
to contain a list of tabs,
In this way can use an Observable later to help control and load state of the view. Or have the enum as part of the state of your app. I am sure there are plenty of resources you can use to find an appropriate solution that meets your needs.
struct MainTabView: View {
@State private var selection: Tabs = .profile
private enum Tabs: Hashable {
case content
case profile
}
var body: some View {
TabView(selection: $selection) {
// Learn Content
Text("The First Tab")
.tabItem {
Image(systemName: "book")
Text("Learn")
}.tag(Tabs.content)
// The Users Profile View.
ProfileView()
.tabItem {
Image(systemName: "person.circle")
Text("Profile")
}.tag(Tabs.profile)
}
}
}
Your code should work, however this is a known issue, from iOS & iPadOS 13 Beta 2 Release Notes:
The tabItemLabel(_:) modifier doesn’t accept @ViewBuilder closures.
The only workaround, until this is fixed, is to use VStack as you've mentioned.
MyView()
.tabItemLabel(VStack {
Image("resourceName")
Text("Item")
})
Update:
This issue was fixed with Xcode 11 beta 3:
The tabItemLabel(:) modifier — now named tabItem(:) — now accepts @ViewBuilder closures.
Example:
.tabItem {
Image(systemName: "circle")
Text("Tab1")
}