I'm trying to build a TabbedView with the following simple code:
TabbedView {
Text("Hello world")
.tabItemLabel(Text("Hello"))
Text("Foo bar")
.tabItemLabel(Text("Foo"))
}
When running, both tabs are visible and enabled but the second tab's ("Foo") content is blank.
Try adding tags:
TabbedView {
Text("Hello world")
.tabItem { Text("Hello") }
.tag(0)
Text("Foo bar")
.tabItem { Text("Foo") }
.tag(1)
}
I was able to fix this by adding a selection state variable and passing that in for the selection:
struct ContentView : View {
#State private var selection = 1
var body: some View {
TabbedView(selection: $selection) {
Text("Tab 1!").tabItemLabel(
Text("Tab 1")).tag(1)
Text("Tab 2!").tabItemLabel(Text("Tab 2")).tag(2)
}
}
}
Now, tapping "Tab 2" will show "Tab 2!" on the screen, as opposed to a blank screen.
This was using Xcode 11.0 beta 2 (11M337n), macOS Catalina 10.15 Beta (19A487l).
In the newest version you should use TabView:
TabView {
AnyView()
.tabItem {
Text("Label 1")
}
AnyView()
.tabItem {
Text("Label 2")
}
}
In Xcode GM, TabbedView was renamed to TabView. So here's the right way to create a tab bar in SwiftUI now:
TabView {
Text("Hello world")
.tabItem { Text("Hello") }
.tag(0)
Text("Foo bar")
.tabItem { Text("Foo") }
.tag(1)
}
Try this way, but you can't use an icon from SF Symbols, use the icons from //icons8.com or from another platform. or watch this tutorial https://www.youtube.com/watch?v=3PfCU5h5z94
struct ContentView : View {
var body : some View {
TabbedView {
Living_R()
.tabItemLabel(VStack {
Image("home")
Text("Home")
}).tag(0)
ContentView()
.tabItemLabel(VStack {
Image("search")
Text("Search")
}).tag(1)
Text("Info")
.tabItemLabel(VStack {
Image("page")
Text("Doc")
}).tag(2)
}
}
}
Related
I'm trying to create a SwiftUI iOS app with a NavigationLink at the top level, and a TabView detail view, that uses .page, so I can swipe between tabs. And yes, I know the TabView is typically at the top level, with NavigationLink detal.
The app runs in the iOS simulator, but hangs/crashes on my iPhone.
Here is the code I want to work:
ContentView.swift
struct ContentView: View {
#State private var selection: String? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(
destination: DetailView(),
tag: "tag1",
selection: $selection) {
Text("Show Detail View")
.font(.largeTitle .bold())
}
}
}
}
}
DetailView.swift
struct DetailView: View {
#State private var selectedTab = "one"
var body: some View {
ZStack {
// 3 main tabs
TabView(selection: $selectedTab) {
Text("Tab 1 View")
.font(.largeTitle .bold())
.tag("one")
Text("Tab 2 View")
.font(.largeTitle .bold())
.tag("two")
Text("Tab 3 View")
.font(.largeTitle .bold())
.tag("three")
}
// no dots
.tabViewStyle(.page)
// .tabViewStyle(PageTabViewStyle())
// .id(UUID())
VStack {
Text("\(selectedTab)")
Spacer()
}
}
}
}
A simplified DetailView also crahses in the same way:
Simplified detailView.swift
struct DetailView: View {
var body: some View {
TabView {
Text("Tab One")
.font(.largeTitle .bold())
.tag("one")
Text("Tab Two")
.font(.largeTitle .bold())
.tag("two")
}
// no dots
.tabViewStyle(.page)
}
}
When I run this on my iPhone, I can select the NavigationLink. it dims then hangs without rendering the detailView(). Much later, I get an error dialog, indicating the app was terminated due to a memory issue.
App runs on device if I remove the tabViewStyle modifier. Of course, it defaults to a traditional tab view with controls on the bottom of the device.
I am running Xcode 13.2.1 (13C100) on an M1 mac mini running macOS Monterey 12.2.1 (21D62). I am running on an iPhone 8 Plus running iOS 15.3.1. Xcode, by default, has an iOS Deployment Target of iOS 15.2.
Any suggestions?
Example using a Picker :
struct DetailView: View {
#State private var selection: Tab = .tabOne
enum Tab {
case tabOne
case tabTwo
}
var body: some View {
TabView(selection: $selection) {
Text("Tab One")
.font(.largeTitle .bold())
.tag(Tab.tabOne)
Text("Tab Two")
.font(.largeTitle .bold())
.tag(Tab.tabTwo)
}
// no dots
.tabViewStyle(.page)
Picker("Details", selection: $selection) {
Image(systemName: "1.circle").tag(Tab.tabOne)
Image(systemName: "2.circle").tag(Tab.tabTwo)
}
.pickerStyle(.segmented)
}
}
I have implemented tab bar in my code. I have see all button in my first tab and from that button i want to switch to second tab programmatically. When I use navigationView then it creates another tab bar and moves to that screen and this changes the index of navigation in swiftui.
struct AppTabNavigation: View {
#State var selection: Tab = .dashboard
var body: some View {
TabView(selection: $selection) {
NavigationView {
FirstTabView()
}.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Label("Home", systemImage: "house.fill")
.accessibility(label: Text("Home"))
}
.tag(Tab.home)
NavigationView {
SecondView()
}.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Label("Home", systemImage: "house.fill")
.accessibility(label: Text("Home"))
}
.tag(second)
}
}
}
Navigation Code:
NavigationLink(destination: AppTabNavigation(selection: Tab.home), isActive: self.$isActiveTabbar){
Text("")
} .isDetailLink(false)
Here is a demo of possible approach - the idea is to move binding for tab selection into view with buttons, so button action could change it.
Tested with Xcode 12 / iOS 14
enum Tab {
case dashboard
case home
case second
}
struct AppTabNavigation: View {
#State var selection: Tab = .home
var body: some View {
TabView(selection: $selection) {
NavigationView {
FirstTabView(tab: $selection)
}.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Label("Home", systemImage: "house.fill")
.accessibility(label: Text("Home"))
}
.tag(Tab.home)
NavigationView {
Text("SecondView")
}.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Label("Home", systemImage: "house.fill")
.accessibility(label: Text("Home"))
}
.tag(Tab.second)
}
}
}
struct FirstTabView: View {
#Binding var tab: Tab
var body: some View {
Button("Go Second") { self.tab = .second }
}
}
I want to create a bottom bar which should be common for all my page. Here I am posting the image:
I know how to do it using storyboard. But how can I do it in SwiftUI?
This kind of view is called tab bar in iOS and in SwiftUI it is called TabView. It is declared like this:
var body: some View {
TabView {
Text("Favourites Screen")
.tabItem {
Image(systemName: "heart.fill")
Text("Favourites")
}
Text("Friends Screen")
.tabItem {
Image(systemName: "person.fill")
Text("Friends")
}
Text("Nearby Screen")
.tabItem {
Image(systemName: "mappin.circle.fill")
Text("Nearby")
}
}
}
Alternatively, you can use Label view for giving name & icon of the tab:
struct BottomTabBar: View {
var body: some View {
TabView{
Text("Profile Contents")
.tabItem{
Label("Profile",systemImage: "person.fill")
}
Text("Tv Contents")
.tabItem{
Label("Tv",systemImage: "tv.fill")
}
}
}
}
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.
I am looking to be able to use a NavigationButton to navigate to a new view within .navigationBarItems. This is how I expect it should work:
NavigationView {
Text("Hello world")
.navigationBarTitle(Text("Title"))
.navigationBarItems(trailing:
NavigationButton(destination: TestView()) {
Text("Next")
}
)
}
However, the "Next" button doesn't do anything! I am aware of PresentationButton which provides a popover view like so:
NavigationView {
Text("Hello world")
.navigationBarTitle(Text("Title"))
.navigationBarItems(trailing:
PresentationButton(destination: TestView()) {
Text("Next")
}
)
}
But this isn't what I'm looking for.
As I told you in comments, It was a bug. But it get fixed and it's working now exactly as you expected since Beta 5, But remember, NavigationButton has changed to NavigationLink. So it would be like:
struct ContentView: View {
var body: some View{
NavigationView {
Text("Hello world")
.navigationBarTitle(Text("Title"))
.navigationBarItems(trailing:
NavigationLink(destination: TestView()) {
Text("Next")
}
)
}
}
}
If you're having a list & you need to navigate through the screens then you should use NavigationLink instead of NavigationButton cause it's changed recently. For example:-
NavigationView{
List(landmarkData) { landmark in
NavigationLink(destination: LandmarkDetail()){
LandmarkRow(landmark: landmark)
}
}
}