TabView changes selection even with empty custom handler - swiftui

The code below changes the selection variable. Why? I thought a custom handler allowed me to intercept everything. The behavior I expect is for nothing to happen when I press a tab item. But what it does is set the selection variable and change the view. What gives?
struct ContentView: View {
#State private var selection = 0
var handler: Binding<Int> { Binding(
get: { selection },
set: { log.info("I don't set the selection variable, the tab changes \($0)") }
) }
var body: some View {
ZStack {
TabView(selection: handler) {
Text("view 0")
.tabItem {
Label("Home", systemImage: "house.fill")
}.tag(0)
Text("view 1")
.tabItem {
Label("Arrows", systemImage: "arrow.left.arrow.right")
}.tag(1)
Text("view 3")
.tabItem {
Label("Arrow Up", systemImage: "arrow.up")
}.tag(2)
}
}
}
}

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()
}
}
}

SwiftUI call function on tab view navigation

I want to execute functions when the user switches tabs in a TabView. Strangely, I can't seem to do this with .simultaneousGesture. How can I execute functions on the event of the user switching tabs in a tab view?
Sample Code:
struct ContentView: View {
#State var selectedTab: Int = 0
var body: some View {
NavigationView {
TabView(selection: $selectedTab) {
Text("View One")
.tabItem {
Text("View One")
}
.simultaneousGesture(TapGesture().onEnded {
print("This isn't printing")
})
Text("View Two")
.tabItem {
Text("View Two")
}
.simultaneousGesture(TapGesture().onEnded {
print("This isn't printing")
})
Text("View Three")
.tabItem {
Text("View Three")
}
.simultaneousGesture(TapGesture().onEnded {
print("This isn't printing")
})
}
}
}
}
EDIT: Updated Code
struct ContentView: View {
#State var selectedTab: Int = 0
var body: some View {
NavigationView {
TabView(selection: $selectedTab) {
Text("View One")
.tabItem {
Text("View One")
}
Text("View Two")
.tabItem {
Text("View Two")
}
Text("View Three")
.tabItem {
Text("View Three")
}
}
}
.onReceive(Just(selectedTab)) { thing in
print("Tapped!!")
print("\(thing)")
}
}
}
Add a .tag modifier to each tab and use a custom Binding that does the work in the setter.
struct ContentView: View {
#State var selectedTab: Int = 0
var body: some View {
NavigationView {
TabView(selection: Binding(
get: { selectedTab },
set: {
selectedTab = $0
print("switched to tab \(selectedTab)")
}
)) {
Text("View One")
.tabItem { Text("1") }
.tag(1)
Text("View Two")
.tabItem { Text("2") }
.tag(2)
Text("View Three")
.tabItem { Text("3") }
.tag(3)
}
}
}
}

How can I update the navigation title when I select a new tab?

I have a tabview with three SwiftUIViews (HomeView, FavoritesView and ContactsView)
each of these views look like this Home View below.
struct HomeView: View {
var body: some View {
VStack {
Image(systemName: "house.fill")
Text("This is the Home View")
}
}
}
My content view looks like this:
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
HomeView()
.tabItem {
Label("Home", systemImage: "house")
.navigationBarTitle("Home")
}
FavoritesView()
.tabItem {
Label("Favorites", systemImage: "star")
.navigationBarTitle("Favorites")
}
ContactsView()
.tabItem {
Label("Contacts", systemImage: "person")
.navigationBarTitle("Contacts")
}
}
}
}
}
But when I run the app each tab get the same "Home" title for the navigation title.
How can I update the navigation title with the correct tab (without adding a navigation View in each SwiftUI Views) I believe we should be able to achieve this with only one nav view? Right???
//Create an enum for your options
enum Tabs: String{
case home
case favorites
case contacts
}
struct TitledView: View {
//Control title by the selected Tab
#State var selectedTab: Tabs = .favorites
var body: some View {
NavigationView {
TabView(selection: $selectedTab) {
Text("HomeView()").tag(Tabs.home)
.tabItem {
Label("Home", systemImage: "house")
}
//Set the tag
.tag(Tabs.home)
Text("FavoritesView()")
.tabItem {
Label("Favorites", systemImage: "star")
}
//Set the tag
.tag(Tabs.favorites)
Text("ContactsView()")
.tabItem {
Label("Contacts", systemImage: "person")
}
//Set the tag
.tag(Tabs.contacts)
}.navigationTitle(selectedTab.rawValue.capitalized)
}
}
}

How to use different tab items for selected/unselected?

How can I make a different tab item for selected and unselected tabs? For example, I would like to use a different image and make the selected text bold.
This is what I have:
struct ContentView: View {
#SceneStorage("selectedTab") private var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
Text("Content 1")
.tabItem {
Label("First", systemImage: "alarm")
.accessibilityHint("Something 1")
}
Text("Content 2")
.tabItem {
Label("Second", systemImage: "calendar")
.accessibilityHint("Something 2")
}
}
}
}
Is there a way a built in way to do this since inside the tab I can't figure out if it is the selected one or not.
Use selectedTab with tag and change your tab image by using the selectedTab condition.
And for Font you can use UITabBarAppearance().
struct ContentView: View {
#State private var selectedTab = 0
init() {
// Set font here of selected and normal
let appearance = UITabBarAppearance()
appearance.stackedLayoutAppearance.normal.titleTextAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 10)]
appearance.stackedLayoutAppearance.selected.titleTextAttributes = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 15)]
UITabBar.appearance().standardAppearance = appearance
}
var body: some View {
TabView(selection: $selectedTab) {
Text("Content 1")
.tabItem {
Label("First", systemImage: selectedTab == 0 ? "alarm" : "alarm_unselected") //<-- Here
.accessibilityHint("Something 1")
}.tag(0) //<-- Here
Text("Content 2")
.tabItem {
Label("Second", systemImage: selectedTab == 1 ? "calendar" : "calendar_unselected") //<-- Here
.accessibilityHint("Something 2")
}.tag(1) //<-- Here
}
}
}

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"){}
}
})
}
}
}