Nav bar with toolbar buttons on a specific tabview item in swift - swiftui

I have a TabView with three views (triangle, square and circle) nested inside a navigation view and link. TabView works fine. I'd like to have toolbar buttons for only a specific tabview; say circle. The toolbar modifier adds the buttons on all the tabviews.
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink() {
TabView {
Text("triangle")
.tabItem {
Label("triangle", systemImage: "triangle")
}
Text("square")
.tabItem {
Label("square", systemImage: "square")
}
Text("circle")
.tabItem {
Label("circle", systemImage: "circle")
}
}
.navigationTitle("Tabs")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button("About") {
print("About tapped!")
}
Button("Help") {
print("Help tapped!")
}
}
}
} label: {
Text("Hello!")
}
.navigationTitle("Title")
}
}
}
How can I set this up to only show toolbar buttons on one tabview only?
I suppose a secondary option (way less preferred) may be to disable the buttons on tabviews where they are not needed (if possible).

It can be done with a selection state for TabView and making visibility of specific toolbar buttons depending on that state.
Here is a demo. Tested with Xcode 13.4 / iOS 15.5
struct ContentView: View {
#State private var selection = 0 // << here !!
var body: some View {
NavigationView {
NavigationLink() {
TabView(selection: $selection) { // << here !!
Text("triangle")
.tabItem {
Label("triangle", systemImage: "triangle")
}.tag(0)
Text("square")
.tabItem {
Label("square", systemImage: "square")
}.tag(1)
Text("circle")
.tabItem {
Label("circle", systemImage: "circle")
}.tag(2)
}
.navigationTitle("Tabs")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
if selection == 2 { // << here !!
Button("About") {
print("About tapped!")
}
Button("Help") {
print("Help tapped!")
}
}
}
}
} label: {
Text("Hello!")
}
.navigationTitle("Title")
}
}
}
Test code in GitHub

Related

how to hide Tabview when using ToolbarItem, preserving ToolbarItem by disappearing

I have two views embedded in a TabView and a third view activated by a ToolbarItem in a navigationStack.
problem 1)
When I tap on plus button I navigate to my addView, but I still can see the tabs at the bottom.
problem 2)
after many test I found that if put the tabView code in MainView Inside a NavigationStack, I solve problem 1) but each time I dismiss from a detailView from a row in ContentView, the navigation Item disappears.
the main view for the tabview
struct MainView: View {
var body: some View {
TabView {
ContentView()
.tabItem {
Label("List", systemImage: "list.dash")
}
SettingsView()
.tabItem {
Label("Settings", systemImage: "gearshape.fill")
}
}
}
}
the ContentView (a list of lessons, navigationDestination goes to a detail view)
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest (sortDescriptors: [
SortDescriptor(\.lessonNuber, order: .reverse)
], predicate: nil) var lessons: FetchedResults<Lesson>
#State var showAddView = false
var body: some View {
NavigationStack {
VStack {
List {
ForEach(lessons, id: \.self) { lesson in
NavigationLink {
DetailView(lesson: lesson)
} label: {
HStack {
Text("\(lesson.lessonNuber)")
.font(.title)
Text( "\(lesson.un_notion)")
.font(.body)
}
}
}
}
// .background(
// NavigationLink(destination: AddView(), isActive: $showAddView) {
// AddView()
// }
// )
.navigationDestination(isPresented: $showAddView) {
AddView()
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
showAddView = true
} label: {
Label("Add Lesson", systemImage: "plus")
}
}
}
}
.padding()
}
}
}

How to switch between tabBar and toolbar swiftUI?

In the files App after you pressed the Select button the tabBar switch to the toolbar.
How can I do this with swiftUI?(Switch between tabBar and toolbar)
struct tabBar: View {
var body: some View {
TabView {
ContentView().tabItem {
Image(systemName: "paperplane.fill")
Text("tabBar")
}
}
.toolbar {
ToolbarItem(placement: .bottomBar, content: {
Button(action: {
}){
Text("toolBar1")
}
})
ToolbarItem(placement: .bottomBar, content: {
Button() {
} label: {
Text("toolBar2")
}
})
}
}
}
For iOS 16 and above, you can use the toolbar(_:for:) method to toggle between visible and hidden states. Also, don't forget to wrap the view in a NavigationView, or else, setting the visibility of the bottom toolbar won't work.
import SwiftUI
struct ContentView: View {
#State var shouldShowTabBar = false
var body: some View {
NavigationView {
TabView {
Group {
Button("Switch Between Tab Bar and Toolbar") {
shouldShowTabBar.toggle()
}
.tabItem {
Label("Tab 1", systemImage: "list.dash")
}
Text("Tab 2")
.tabItem {
Label("Tab 2", systemImage: "square.and.pencil")
}
}
.toolbar {
ToolbarItem(placement: .bottomBar) {
Button {
} label: {
Text("Toolbar Button")
}
}
}
.toolbar(shouldShowTabBar ? .visible : .hidden, for: .tabBar)
.toolbar(shouldShowTabBar ? .hidden : .visible, for: .bottomBar)
}
}
}
}
If you have to support iOS 14 and 15, you can check every item if it should be visible and hide/show them one by one.
import SwiftUI
struct ContentView: View {
#State var shouldShowTabBar = false
var body: some View {
NavigationView {
TabView {
Group {
Button("Switch Between Tab Bar and Toolbar") {
shouldShowTabBar.toggle()
}
.tabItem {
if shouldShowTabBar {
Label("Tab 1", systemImage: "list.dash")
}
}
Text("Tab 2")
.tabItem {
if shouldShowTabBar {
Label("Tab 2", systemImage: "square.and.pencil")
}
}
}
.toolbar {
ToolbarItem(placement: .bottomBar) {
if !shouldShowTabBar {
Button {
} label: {
Text("Toolbar Button")
}
}
}
}
}
}
}
}

How to change TabView color for each icon?

I've tried this to try to change the color of the tab icons individually, but for some reason, the color will modify it correctly and then after tapping back to the icon, it will not display the customized color.
How would I go about changing the tab items icons for each individual tab (different colors for each)?
Here's the code for the view holding the TabView that I'm trying to modify.
struct MainView: View {
#AppStorage("PendingOnboarding") var pendingOnboarding = true
init() {
UIPageControl.appearance().currentPageIndicatorTintColor = UIColor(Color.recyclepediaGreen)
}
var body: some View {
NavigationView {
ZStack {
TabView {
CurbsideView()
.tabItem {
Label("Curbside", systemImage: "car.fill")
}
ItemsView()
.tabItem {
Label("Items", systemImage: "archivebox.fill")
}
LearnView()
.tabItem {
Label("Learn", systemImage: "info.circle.fill")
}
ContactUsView()
.tabItem {
Label("Contact Us", systemImage: "phone.fill.connection")
}
}
.accentColor(Color.recyclepediaBlue)
.toolbar {
ToolbarItem(placement: .principal) {
Image("Recyclepedia")
.resizable()
.scaledToFit()
.padding(.top, 5)
.padding(.bottom, 5)
}
}
}
.popup(isPresented: $pendingOnboarding, dragToDismiss: false, closeOnTap: false, backgroundColor: Color.white) {
OnboardingView(pendingOnboarding: $pendingOnboarding)
}
}
}
}
The easiest solution is to use an enum that returns a Color. Like this:
struct TestTabviewIconColor: View {
#State var pageIndex: TabColor = .red
var body: some View {
VStack {
Text("Current page is \(pageIndex.rawValue)")
TabView(selection: $pageIndex) { // Use the Tabview(selection:) init
Text("The First Tab: \(pageIndex.color().description)")
.tabItem {
Image(systemName: "1.square.fill")
Text("First")
}
.tag(TabColor.red)
Text("Another Tab: \(pageIndex.color().description)")
.tabItem {
Image(systemName: "2.square.fill")
Text("Second")
}
.tag(TabColor.green)
Text("The Last Tab: \(pageIndex.color().description)")
.tabItem {
Image(systemName: "3.square.fill")
Text("Third")
}
.tag(TabColor.teal)
}
.accentColor(pageIndex.color())
.font(.headline)
}
}
}
// Define your enum here
enum TabColor: Int {
case red = 0
case green = 1
case teal = 2
func color() -> Color {
switch self {
case .red:
return .red
case .green:
return .green
case .teal:
return .teal
}
}
}
As a bonus, you can select your tab by using it's color simply by calling:
pageIndex = .green

SwiftUI Toolbar In a TabBar View

My first view has a NavigationView with a Tab Bar View
struct TestView: View {
var body: some View {
NavigationView {
TabTestView()
}
}
}
In the Tab Bar, I have two views, say TestView1 and TestView2.
struct TabTestView: View {
var body: some View {
TabView {
TestView1()
.tabItem {
Label("Test 1", systemImage: "list.dash")
}
TestView2()
.tabItem {
Label("Test 2", systemImage: "plus")
}
}
}
}
I now need to add a toolbar button to TestView1. Here is my attempt to do so:
struct TestView1: View {
var body: some View {
Text("Hello World")
.navigationTitle("Test View 1")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") {
print("Toolbar button click")
}
}
}
}
}
While this adds the Navigation Title to the page, the toolbar button is not added, as shown in the screenshot below:
If I, however, add the toolbar to the TabTestView, as below, the toolbar button does show up.
struct TabTestView: View {
var body: some View {
TabView {
TestView1()
.tabItem {
Label("Test 1", systemImage: "list.dash")
}
TestView2()
.tabItem {
Label("Test 2", systemImage: "plus")
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") {
print("Toolbar button click")
}
}
}
}
}
This is a problem - the code to be executed on the button click depends upon variables in TestView1, and adding the toolbar to the TabTestView doesn't feel right, semantically the toolbar "belongs" in TestView1.
Any thoughts would be greatly appreciated.
Once I had working code, I realized I had seen this before. It appears to be a bug in SwiftUI. TabView and NavigationView don't play well together. You will find a lot of my answer will say one NavigationViews at the top of the view hierarchy, which is what you have done. That will not work in this instance. The workaround is to put a NavigationViews in each of the views in the tabs separately for this to work. You will still get all of the correct NavigationViews behavior for each of those hierarchies, but it is duplicative. I don't think I ever filed a Radar on it, but I will today and will post the Radar number here.
struct TestView: View {
var body: some View {
// NavigationView { //Remove this NavigationView
TabTestView()
// }
}
}
struct TabTestView: View {
var body: some View {
TabView {
TestView1()
.tabItem {
Label("Test 1", systemImage: "list.dash")
}
TestView2()
.tabItem {
Label("Test 2", systemImage: "plus")
}
}
}
}
struct TestView1: View {
var body: some View {
NavigationView { // Add here
Text("Hello World 1")
.navigationTitle("Test View 1")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") {
print("Toolbar button click")
}
}
}
}
}
}
struct TestView2: View {
var body: some View {
NavigationView { // Add here
Text("Hello World 2")
.navigationTitle("Test View 2")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") {
print("Toolbar button click")
}
}
}
}
}
}
The Radar number is FB9727010.

Dynamically set SwiftUI NavigationBarItems?

How can you dynamically change SwiftUI Navigation Bar Items?
I have a TabView within a NavigationView, and I would like the Navigation Bar Items to change depending on tab that is selected. However, I am having a hard time determining how to change this with .onAppear(), assuming that is even what you are suppose to do.
My code is currently laid out as follows:
var body: some View {
NavigationView {
TabView {
contentWithNavigationButtons()
.tabItem {
Image(systemName: "house")
Text("Buttons")
}
contentWithoutNavigationButtons()
.tabItem {
Image(systemName: "person")
Text("No Buttons")
}
.onAppear {
//Navigation Bar Items should be removed
}
}
.navigationBarItems(trailing: someButton)
}
Here is a demo of possible solution - add tracking selection for tabs and make button depending of tab selection. Tested with Xcode 12 / iOS 14
struct DemoView: View {
#State private var selection = 0 // << here !!
var body: some View {
NavigationView {
TabView(selection: $selection) {
contentWithNavigationButtons()
.tabItem {
Image(systemName: "house")
Text("Buttons")
}.tag(0) // << here !!
contentWithoutNavigationButtons()
.tabItem {
Image(systemName: "person")
Text("No Buttons")
}.tag(1)
}
.navigationBarItems(trailing: Group {
if selection == 0 { // << here !!
Button("Some"){}
}
})
}
}
}