Disable item in TabView SwiftUI - swiftui

How Can I set an item to disabled (not clickable) but visible in my tabView ?
TabView(selection: $selectedTab) {
Settings()
.tabItem {
Image(systemName: "gearshape.fill")
Text("Settings")
}.tag(1)
.disabled(true) // Not Working

I just create a way to do what you want fully supported and customisable!
test with Xcode Version 12.1, iOS 14.1, Here goes:
import SwiftUI
struct ContentView: View {
#State private var selection = 0
#State private var exSelection = 0
private var disableThis = 2
var body: some View
{
TabView(selection: $selection)
{
viewFinder(selectedIndex: selection == disableThis ? $exSelection : $selection)
.tabItem { Image(systemName: "1.circle") }
.tag(0)
viewFinder(selectedIndex: selection == disableThis ? $exSelection : $selection)
.tabItem { Image(systemName: "2.circle") }
.tag(1)
viewFinder(selectedIndex: selection == disableThis ? $exSelection : $selection)
.tabItem { Image(systemName: "3.circle") }
.tag(2)
viewFinder(selectedIndex: selection == disableThis ? $exSelection : $selection)
.tabItem { Image(systemName: "4.circle") }
.tag(3)
}
.onAppear()
{
UITabBar.appearance().barTintColor = .white
}
.accentColor(selection == disableThis ? Color.gray : Color.red)
.onChange(of: selection) { _ in
if selection != disableThis { exSelection = selection } else { selection = exSelection }
}
}
}
struct viewFinder: View
{
#Binding var selectedIndex: Int
var body: some View {
return Group
{
if selectedIndex == 0
{
FirstView()
}
else if selectedIndex == 1
{
SecondView()
}
else if selectedIndex == 2
{
ThirdView()
}
else if selectedIndex == 3
{
FourthView()
}
else
{
EmptyView()
}
}
}
}
struct FirstView: View { var body: some View {Text("FirstView")}}
struct SecondView: View { var body: some View {Text("SecondView")}}
struct ThirdView: View { var body: some View {Text("ThirdView")}}
struct FourthView: View { var body: some View {Text("FourthView")}}

There is not direct SwiftUI instrument for this now (SwiftUI 2.0), so find below possible approach based on TabBarAccessor from my another answer https://stackoverflow.com/a/59972635/12299030.
Tested with Xcode 12.1 / iOS 14.1 (note - tint color changed just for demo because disabled item is grey and invisible on grey tabbar)
struct TestTabBar: View {
init() {
UITabBar.appearance().unselectedItemTintColor = UIColor.green
}
#State private var selection = 0
var body: some View {
TabView(selection: $selection) {
Text("First View")
.background(TabBarAccessor { tabBar in
tabBar.items?.last?.isEnabled = false // << here !!
})
.tabItem { Image(systemName: "1.circle") }
.tag(0)
Text("Second View")
.tabItem { Image(systemName: "2.circle") }
.tag(1)
}
}
}

Related

SwiftUI navigationStack in tabView

I have a question about the new NavigationStack in IOS 16. I have a problem setting navigationTitle in ContentView, I set navigationTitle but it is the same for all tabs. Can I set it somehow so that I can edit it for each different tab? Using tag ? thank you very much
struct ContentView: View {
#State var selection = 1
var body: some View {
NavigationStack {
TabView(selection: $selection) {
LaunchView()
.badge(2)
.tabItem {
Label("Received", systemImage: "tray.and.arrow.down.fill")
}
.tag(1)
DeView()
.tabItem {
Label("Sent", systemImage: "tray.and.arrow.up.fill")
}
.tag(2)
DeView()
.tabItem {
Label("Sent", systemImage: "tray.and.arrow.up.fill")
}
.tag(3)
}
.navigationTitle("test")
}
}
}
You can create a function that returns the title for every selection:
func title() -> String {
if selection == 1 {
return title
}else if selection == 2 {
return some Title
}else if selection == 3 {
return some other Title
}
}
Or my personal best way: Enums!
Create an enum that holds the tabs, then create a title property for each tab:
struct ContentView: View {
#State var selection = Tab.received
var body: some View {
NavigationStack {
TabView(selection: $selection) {
Text("hello")
.badge(2)
.tabItem {
Label("Received", systemImage: "tray.and.arrow.down.fill")
}
.tag(Tab.received)
Text("hello3")
.tabItem {
Label("Sent", systemImage: "tray.and.arrow.up.fill")
}
.tag(Tab.sent)
Text("hello5")
.tabItem {
Label("Sent", systemImage: "tray.and.arrow.up.fill")
}
.tag(Tab.sennt)
}.navigationTitle(selection.title)
}
}
}
enum Tab: Int {
case received = 1
case sent = 2
case sennt = 3
var title: String {
switch self {
case .received:
return "Hello"
case .sent:
return "Hello World"
case .sennt:
return "Hello, World!"
}
}
}
Plus it’s easier to work with than Ints.
Edit: To hide the TabBar for DeviceView:
struct Test: View {
#State var selection = 1
#State var devicePresented = false
var body: some View {
NavigationStack {
//Content
.navigationDestination(isPresented: $devicePresented) {//present DeviceView when devicePresented is true
DeviceView()
}
}
}
}
struct SettingsView: View {
#Binding var devicePresented: Bool
var body: some View {
List {
Button(action: {
devicePresented.toggle()
}) {
Text("Go to device")
}
}
}
}

How to hide TabView navigating from tabItem in childview in SwiftUI?

I am using TabView in swiftui. I want navigate child view from tabview tabItem. When I navigate from taview to childview, it shows tabview in bottom. Here is the image.. that click login goes to Myview page. But in Myview, Tabview is not hiding
Here is my code..
Tabview
struct WelcomeView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
Image("home_icon")
Text("Home")
}
.tag(0)
NotificationView()
.tabItem {
Image("notification_icon")
Text("Notification")
}.tag(1)
AccountView()
.tabItem {
Image("account_icon")
Text("Account")
}.tag(2)
SettingView()
.tabItem {
Image("settings_icon")
Text("Setting")
}.tag(3)
}
}
}
In SettingView tabItem:
struct SettingView: View {
#State private var isActive = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
VStack {
Button(action: {
isActive = true
}) {
Text("Login")
}
NavigationLink("", destination: MyView(), isActive: $isActive)
}
}
}
}
}
MyView:
struct MyView: View {
var body: some View {
ZStack{
Text("Hello My View")
}.navigationBarHidden(true)
.navigationBarTitleDisplayMode(.inline)
}
}
When I click login Button in SettingView tabItem, It goes to MyView page. But in MyView page tabview is not hide.
How to hide tabview from MyView page?
To hide the tab we can add a Bool that will take care of showing the view or not. Then by using #Binding we can pass it to the other child views, whatever changes you make down the chain will affect all the views.
struct WelcomeView: View {
#State var isTabViewShown = true
var body: some View {
TabView {
if isTabViewShown {
HomeView()
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}
.tag(0)
NotificationView()
.tabItem {
Image(systemName: "envelope.open.fill")
Text("Notification")
}.tag(1)
AccountView()
.tabItem {
Image(systemName: "person.crop.circle")
Text("Account")
}.tag(2)
SettingView(isTabViewShown: $isTabViewShown)
.tabItem {
Image(systemName: "gearshape")
Text("Setting")
}.tag(3)
}
}
}
}
struct MyView: View {
#Binding var isTabViewShown: Bool
var body: some View {
ZStack{
Text("Hello My View")
}
.navigationBarHidden(false)
.navigationBarTitleDisplayMode(.inline)
.onAppear {
isTabViewShown = false
}
}
}
struct SettingView: View {
#State private var isActive = false
#Binding var isTabViewShown: Bool
var body: some View {
NavigationView {
VStack(alignment: .leading) {
VStack {
Button(action: {
isActive = true
}) {
Text("Login")
}
NavigationLink("", destination: MyView(isTabViewShown: $isTabViewShown), isActive: $isActive)
//Here is a button if you want to show it again
Button(action: {
isTabViewShown.toggle()
//You can also use this is you don't want to use a toggle
// isTabViewShown = true
}) {
Text("Show again")
}
}
}
}
}
}
struct HomeView: View {
var body: some View {
Text("Home View")
}
}
struct NotificationView: View {
var body: some View {
Text("Notification View")
}
}
struct AccountView: View {
var body: some View {
Text("Account View")
}
}

SwiftUI: Changing Light/Dark Mode in Sheet Does Not Refresh Sheet UI

I want to have a system/light/dark mode picker in a sheet, along with other settings.
Selecting a different scheme does correctly change the UI in the ContentView (behind the sheet), but the sheet itself retains the old scheme and must be dismissed and opened again.
What am I doing wrong?
Example:
import SwiftUI
struct ContentView: View {
#EnvironmentObject var viewManager: ViewManager
#State var showSettingsSheet = false
var body: some View {
NavigationView {
List {
Text("Some content 1")
Text("Some content 2")
}
.toolbar {
ToolbarItem {
Button(action: {
showSettingsSheet.toggle()
}){
Image(systemName: "gearshape.fill")
}
.sheet(isPresented: $showSettingsSheet){SettingsSheet()}
}
}
}
.preferredColorScheme(viewManager.colorScheme == "Light" ? .light : viewManager.colorScheme == "Dark" ? .dark : nil)
}
}
struct SettingsSheet: View {
#Environment (\.presentationMode) var presentationMode
#EnvironmentObject var viewManager: ViewManager
var body: some View {
NavigationView{
GroupBox {
Picker("Display Mode", selection: $viewManager.colorScheme) {
Text("System").tag("System")
Text("Light").tag("Light")
Text("Dark").tag("Dark")
}
.pickerStyle(.segmented)
}
.toolbar {
ToolbarItem {
Button(action: {
presentationMode.wrappedValue.dismiss()
}){
Text("Done")
}
}
}
}
}
}
class ViewManager: ObservableObject {
#AppStorage("colorScheme") var colorScheme: String = "System"
}
You have to provide colorScheme to sheet manually as it is outside your view hierarchy, i.e.:
struct ContentView: View {
#EnvironmentObject var viewManager: ViewManager
#State var showSettingsSheet = false
var body: some View {
NavigationView {
List {
Text("Some content 1")
Text("Some content 2")
}
.toolbar {
ToolbarItem {
Button(action: {
showSettingsSheet.toggle()
}){
Image(systemName: "gearshape.fill")
}
.sheet(isPresented: $showSettingsSheet){
SettingsSheet()
.preferredColorScheme(viewManager.colorScheme == "Light" ? .light : viewManager.colorScheme == "Dark" ? .dark : nil) // HERE
}
}
}
}
.preferredColorScheme(viewManager.colorScheme == "Light" ? .light : viewManager.colorScheme == "Dark" ? .dark : nil)
}
}

SwiftUI problem with not reloading Tab Item when using NavigationLink

I'm using Navigation View inside TabView and the problem is that if I an on Tab A and with NavigationView I open other Views, when changing from tab A to B and after a while I came back to tab A I dosen't reload tab A from beginning but it show the last View open with NavitagionLink. The problem is that in each View I'm getting data from a DB and because of that if shows an empty view.
My ContentView looks like this:
struct ContentView: View {
#ObservedObject var appState = AppState()
#State var currentTab : Tab
var body: some View {
TabView(selection: $appState.currentTab) {
NavigationView {
HomeView(appState: appState)
}
.tabItem {
if appState.currentTab == .home {
Image(systemName: "house.fill")
} else {
Image(systemName: "house")
}
Text(LocalizedStringKey("HomeTabMenu"))
}.tag(Tab.home)
NavigationView {
SearchView()
}
.tabItem {
if appState.currentTab == .search {
Image(systemName: "magnifyingglass.circle.fill")
} else {
Image(systemName: "magnifyingglass")
}
Text(LocalizedStringKey("SearchTabMenu"))
}.tag(Tab.search)
NavigationView {
AddItemView(appState: appState)
}
.tabItem {
if appState.currentTab == .add {
Image(systemName: "plus.circle.fill")
} else {
Image(systemName: "plus.circle")
}
Text(LocalizedStringKey("SellTabMenu"))
}.tag(Tab.add)
NavigationView {
ShoppingCartFavoritesView()
}
.tabItem {
if appState.currentTab == .favorites {
Image(systemName: "cart.fill")
} else {
Image(systemName: "cart")
}
Text(LocalizedStringKey("CartTabMenu"))
}.tag(Tab.favorites)
NavigationView {
ProfileView(appState: appState)
}
.tabItem {
if appState.currentTab == .profile {
Image(systemName: "person.fill")
} else {
Image(systemName: "person")
}
Text(LocalizedStringKey("ProfileTabMenu"))
}.tag(Tab.profile)
}//End TabView
.accentColor(Color("ColorMainDark"))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(currentTab: Tab.home)
}
}
class AppState: ObservableObject {
#Published var currentTab : Tab = .home
}
enum Tab {
case home, search, add, favorites, profile
}
And if I open SearchView()
struct SearchView: View {
var body: some View {
NavigationLink(destination: View_2(id: "ABC")){
Text("ABC")
}
}
}
struct View_2: View {
#ObservedObject var performSearchProducts = PerformSearchInProducts()
var id : String
var body: some View {
ScollView {
ForEach(performSearchProducts.products) { product in
Text(product.name)
}
}.onAppear(perform: {
self.performSearchProducts.searchSubCategory(id: id)
})
}
}
If in SearchView I'm on View_2() and the I open another Tab, when I come back to tab SearchView it doesn't show the SearchView(), but it remains on View_2() with the back button in navigation bar.
How can I make to show SearchView() and not keep the state of NavigationLink?
It's the default behavior. Attach id to TabView.
}//End TabView
.accentColor(Color("ColorMainDark"))
.id(appState.currentTab) //<--Here

SwiftUI tab view display sheet

I'm new to SwiftUI and I tried to build a tab bar that contained a tab that will return a modal(sheet) but not view. After I tried I found sometimes it will work but sometime are not. I want to make the previous tabbed item as the selected tab after the user dismissed the modal. But I can't find what the error. Anyone explains to me what the problem of my code?
import SwiftUI
struct ContentView: View {
#State var isPresenting = false
#State private var selectedItem = 1
#State private var oldSelectedItem = 1
var body: some View {
TabView(selection: $selectedItem){
Text("1")
.tabItem {
Image(systemName: "house")
}.tag(1)
.onAppear {
self.oldSelectedItem = self.selectedItem
}
Text("") // I want this to display the sheet.
.tabItem { Image(systemName: "plus.circle") }
.tag(2)
.onAppear {
self.isPresenting = true
self.selectedItem = self.oldSelectedItem
}
Text("3")
.tabItem {
Image(systemName: "calendar")
}.tag(3)
.onAppear {
self.oldSelectedItem = self.selectedItem
}
}
.sheet(isPresented: $isPresenting) {
testSheet
}
.accentColor(Color.orange)
}
var testSheet : some View {
VStack{
Text("testing")
}
}
}
A possible solution is to use TabView selection to activate sheet programmatically, but do not actually allow this selection to be changed (tested with Xcode 12 / iOS 14).
Update: retested with Xcode 13.4 / iOS 15.5
struct ContentView: View {
#State var isPresenting = false
#State private var selectedItem = 1
#State private var oldSelectedItem = 1
var body: some View {
TabView(selection: $selectedItem){
Text("1")
.tabItem {
Image(systemName: "house")
}.tag(1)
Text("") // I want this to display the sheet.
.tabItem { Image(systemName: "plus.circle") }
.tag(2)
Text("3")
.tabItem {
Image(systemName: "calendar")
}.tag(3)
}
// .onReceive(Just(selectedItem)) // SwiftUI 1.0 - import Combine for this
.onChange(of: selectedItem) { // SwiftUI 2.0 track changes
if 2 == selectedItem {
self.isPresenting = true
} else {
self.oldSelectedItem = $0
}
}
.sheet(isPresented: $isPresenting, onDismiss: {
self.selectedItem = self.oldSelectedItem
}) {
testSheet
}
.accentColor(Color.orange)
}
var testSheet : some View {
VStack{
Text("testing")
}
}
}
A small change to Martijn Pieters's answer:-
The original code changes the current tab to a blank tab behind the sheet. This update addresses this issue by keeping the last selected tab alive.
struct ContentView: View {
#State var isPresenting = false
#State private var selectedItem = 1
#State private var oldSelectedItem = 1
var body: some View {
TabView(selection: $selectedItem){
Text("1")
.tabItem {
Image(systemName: "house")
}.tag(1)
Text("") // I want this to display the sheet.
.tabItem { Image(systemName: "plus.circle") }
.tag(2)
Text("3")
.tabItem {
Image(systemName: "calendar")
}.tag(3)
}
// .onReceive(Just(selectedItem)) // SwiftUI 1.0 - import Combine for this
.onChange(of: selectedItem) { // SwiftUI 2.0 track changes
if 2 == selectedItem {
self.isPresenting = true
self.selectedItem = self.oldSelectedItem
} else if (isPresented == false) {
self.oldSelectedItem = $0
}
}
.sheet(isPresented: $isPresenting) {
testSheet
}
.accentColor(Color.orange)
}
var testSheet : some View {
VStack{
Text("testing")
}
}
}
After user clicks on the sheet option, the onChange listener restores self.oldselecteditem as the active tab. The dismiss event listener on the sheet has been removed since the last active tab is already active and the listener would serve no purpose.
Add the code below to your TabView
.onChange(of: selectedItem) { newValue in
if newValue == 2 {
isPresenting = true
selectedItem = oldSelectedItem
} else {
oldSelectedItem = newValue
}
}
.sheet(isPresented: $isPresenting) {
// Sheet view
}