I created a TabView with 4 items but with iOS 13.x only the first view is displayed correctly.
When I click on another item's icon, the view is not shown correctly but the app only shows a white view. If I run the app on iOS > 14 I can correctly view all the views.
TabView implementation:
struct ContentView: View {
private enum Tab: Hashable {
case discovery
case qrcode
case devices
case settings
}
#State private var selectedTab: Tab = .discovery
var body: some View {
NavigationView {
TabView(selection: $selectedTab) {
DiscoveryView()
.tabItem {
VStack {
Image(systemName: "lock.rotation.open")
Text("Discovery")
}
}
.tag(0)
QrCodeView()
.tabItem {
VStack {
Image(systemName: "qrcode.viewfinder")
Text("QrCode")
}
}
.tag(1)
DevicesView()
.tabItem {
VStack {
Image(systemName: "qrcode.viewfinder")
Text("My devices")
}
}
.tag(2)
SettingsView()
.tabItem {
VStack {
Image(systemName: "gear")
Text("Settings")
}
}
.tag(3)
}
}
}
Implementation of one of the views:
struct QrCodeView: View {
var body: some View {
Text("QrCode")
}
}
Where am I doing wrong?
It might be a reason of selection... selection and tag types should be the same, so try
#State private var selectedTab: Tab = .discovery
var body: some View {
NavigationView {
TabView(selection: $selectedTab) {
DiscoveryView()
.tabItem {
VStack {
Image(systemName: "lock.rotation.open")
Text("Discovery")
}
}
.tag(.discovery) // << here !!
QrCodeView()
.tabItem {
VStack {
Image(systemName: "qrcode.viewfinder")
Text("QrCode")
}
}
.tag(.qrcode) // << here !!
// ... others the same
Considering the simplest possible tab view app in SwiftUI:
struct ContentView: View {
#State private var selection = 0
var body: some View {
TabView(selection: $selection){
Text("First View")
.font(.title)
.tabItem {
VStack {
Image("first")
Text("First")
}
}
.tag(0)
Text("Second View")
.font(.title)
.tabItem {
VStack {
Image("second")
Text("Second")
}
}
.tag(1)
}
}
}
Whenever a tab is tapped, the tab items are reloaded and you have the opportunity to change the image for a selected tab if you needed by comparing the selection with the tag. However, if you have a dynamic number of tabs in a variable and use a ForEach to display them, that doesn't work:
struct ContentView: View {
#State private var selection = 0
private var items: [AnyView] {
return [
AnyView(Text("First View")
.font(.title)
.tabItem {
VStack {
Image("first1")
Text("First1")
}
}
.tag(0)),
AnyView(Text("Second View")
.font(.title)
.tabItem {
VStack {
Image("second")
Text("Second")
}
}
.tag(1))
]
}
var body: some View {
TabView(selection: $selection){
ForEach(0..<self.items.count) { index in
self.items[index]
}
}
}
}
The body of the ForEach is not called when the view reloads. Is there a way to accomplish changing an image while also using a ForEach in your TabView?
Try the following:
struct ContentView: View {
#State private var selection = 0
var body: some View {
TabView(selection: $selection){
Text("First View")
.font(.title)
.tabItem {
VStack {
Image(systemName: selection == 0 ? "xmark" : "plus")
Text("First")
}
}
.tag(0)
Text("Second View")
.font(.title)
.tabItem {
VStack {
Image(systemName: selection == 1 ? "xmark" : "minus")
Text("Second")
}
}
.tag(1)
}
}
}
The answer is in the docs! When using a ForEach with a range:
/// Creates an instance that computes views on demand over a *constant*
/// range.
///
/// This instance only reads the initial value of `data` and so it does not
/// need to identify views across updates.
///
/// To compute views on demand over a dynamic range use
/// `ForEach(_:id:content:)`.
So to make this work, use ForEach(_:id:content:).
I am trying to make an app using the TabView. The app renders and runs nicely, except for the fact that tapping on the tabs does nothing.
Here is my code, am I missing something?
TabView {
HomeView()
.tabItem {
VStack {
Image(systemName: "1.circle")
Text("Home")
}
}.tag(1)
SecondView()
.tabItem {
VStack {
Image(systemName: "2.circle")
Text("SecondView")
}
}.tag(2)
}
I had the same issue, in the end it turned out I had the accessibility option "Full keyboard access" turned on. Switching this off fixed it.
Here is a minimal example which works fine for me:
struct HomeView: View {
var body: some View {
Text("Home")
}
}
struct SecondView: View {
var body: some View {
Text("SecondView")
}
}
struct ContentView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
VStack {
Image(systemName: "1.circle")
Text("Home")
}
}.tag(1)
SecondView()
.tabItem {
VStack {
Image(systemName: "2.circle")
Text("SecondView")
}
}.tag(2)
}
}
}
I hope this helps!
I'm trying to create a TabView that contains a NavigationView. However, the navigation bar does not reach the top of the screen. How can I fix this?
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
TestView()
}
.tabItem {
Image(systemName: "star")
Text("Tab 1")
}
Text("Tab 2")
.tabItem {
Image(systemName: "star")
Text("Tab 2")
}
Text("Tab 3")
.tabItem {
Image(systemName: "star")
Text("Tab 3")
}
}
}
}
struct TestView: View {
var body: some View {
List {
Text("Hello")
}
.navigationBarTitle("Title")
}
}
The following view modifier will help you
.edgesIgnoringSafeArea(.top)
struct ContentView : View {
var body: some View {
NavigationView {
TabbedView {
PasswordGenerator()
.tabItemLabel {
Image("KeyGlyph")
Text("Generator")
}
PasswordGeneratorSettings()
.tabItemLabel {
Image("SettingsGlyph")
Text("Settings")
}
}
}
}
}
This won't compile but it was used in the Swift Essentials video at WWDC (See minute 54:30) and I've seen some workarounds like the VStack workaround (but even that has many flaws, the left tab is too far to the left and the right tab is too far to the right and when switching tabs only the first one that initially loaded loads and the other tab stays blank and using tags doesn't help). So how do I have two tabs that load the views and have an Image and Text?
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)
}
}
}
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...
}
}
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")
}
TabbedView was deprecated.
You could use TabView instead now
struct AppTabbedView: View {
#State private var selection = 3
var body: some View {
TabView (selection:$selection){
Text("The First Tab")
.tabItem {
Image(systemName: "1.square.fill")
Text("First")
}
.tag(1)
Text("Another Tab")
.tabItem {
Image(systemName: "2.square.fill")
Text("Second")
}.tag(2)
Text("The Last Tab")
.tabItem {
Image(systemName: "3.square.fill")
Text("Third")
}.tag(3)
}
.font(.headline)
}
}
as of Xcode 11 GM, this code works: (from https://developer.apple.com/documentation/swiftui/tabview)
TabView {
Text("The First Tab")
.tabItem {
Image(systemName: "1.square.fill")
Text("First")
}
Text("Another Tab")
.tabItem {
Image(systemName: "2.square.fill")
Text("Second")
}
Text("The Last Tab")
.tabItem {
Image(systemName: "3.square.fill")
Text("Third")
}
}
.font(.headline)
TabbedView is deprecated and has been renamed to TabView
You can use TabView like this
TabView {
(Text("Tab 1!").tabItem {
Text("First")
})
(Text("Tab 2!").tabItem {
Text("Second")
})
}
The 'TabbedView' can be used in a way similar to the following:
struct TabView : View {
#State private var selection = 1
var body: some View {
TabbedView (selection: $selection) {
InboxList()
.tabItemLabel(selection == 1 ? Image("second") : Image("first"))
.tag(1)
PostsList()
.tabItemLabel(Image("first"))
.tag(2)
Something()
.tabItemLabel(Image("first"))
.tag(3)
}
}
}
I'm not sure why your example doesn't compile but you miss both the selection param and the .tag property on each "tab".
Btw in the current XCode version (beta 2) you cannot show both a text and an image as label.