NavigationLink is getting randomly popped and "Unable to present. Please file a bug." - ios

I have 6 navigation links within a navigation view but on some of them it randomly pops back to the root and says "Unable to present. Please file a bug." in the console. Here is my code for my NavigationView:
NavigationView {
VStack {
NavigationLink(
destination: EventSetupQueueSelectionView()
.background(Color.neutral(.oneHundred))
.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
,
tag: 1,
selection: $modelController.currentPage,
label: { EmptyView() }
)
NavigationLink(
destination: EventSetupQueueDetailsView()
.background(Color.neutral(.oneHundred))
.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
,
tag: 2,
selection: $modelController.currentPage,
label: { EmptyView() }
)
NavigationLink(
destination: EventSetupSigningView()
.background(Color.neutral(.oneHundred))
.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
,
tag: 3,
selection: $modelController.currentPage,
label: { EmptyView() }
)
NavigationLink(
destination: EventSetupRetailView()
.background(Color.neutral(.oneHundred))
.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
,
tag: 4,
selection: $modelController.currentPage,
label: { EmptyView() }
)
NavigationLink(
destination: EventSetupPrivacyView()
.background(Color.neutral(.oneHundred))
.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
,
tag: 5,
selection: $modelController.currentPage,
label: { EmptyView() }
)
NavigationLink(
destination: EventSetupSummaryView()
.background(Color.neutral(.oneHundred))
.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
,
tag: 6,
selection: $modelController.currentPage,
label: { EmptyView() }
)
EventSetupDetailView()
.background(Color.neutral(.oneHundred))
.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
I've seen other stackoverflow/apple dev forum answers that talk about adding this to the NavigationView but it doesn't work for me:
NavigationLink(destination: EmptyView()) {
EmptyView()
}
How can I fix this?

That's not how you're meant to work with NavigationLink + selection. It should be used when you're selecting one of destinations from current screen, it's not meant to be changed while an other screen presenting. And in your case you're pushing new destination before previous disappeared, that's why it's not working.
I suggest you Chaining your views into a navigation stack:
struct ContentView: View {
var body: some View {
NavigationView {
EventSetupDetailView()
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.background(Color.yellow)
.onAppear {
print("hello")
}
}
}
}
struct EventSetupDetailView: View {
var body: some View {
NavigationLink(
destination: EventSetupQueueSelectionView()
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.background(Color.yellow)
) {
Text("next")
}
}
}
struct EventSetupQueueSelectionView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button("Back") {
presentationMode.wrappedValue.dismiss()
}
NavigationLink(
destination: EventSetupQueueDetailsView()
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.background(Color.yellow)
) {
Text("next")
}
}
}
struct EventSetupQueueDetailsView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button("Back") {
presentationMode.wrappedValue.dismiss()
}
NavigationLink(
destination: EventSetupSigningView()
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.background(Color.yellow)
) {
Text("next")
}
}
}
struct EventSetupSigningView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button("Back") {
presentationMode.wrappedValue.dismiss()
}
NavigationLink(
destination: EventSetupRetailView()
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.background(Color.yellow)
) {
Text("next")
}
}
}
struct EventSetupRetailView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button("Back") {
presentationMode.wrappedValue.dismiss()
}
NavigationLink(
destination: EventSetupPrivacyView()
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.background(Color.yellow)
) {
Text("next")
}
}
}
struct EventSetupPrivacyView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button("Back") {
presentationMode.wrappedValue.dismiss()
}
NavigationLink(
destination: EventSetupSummaryView()
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.background(Color.yellow)
) {
Text("next")
}
}
}
struct EventSetupSummaryView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button("Back") {
presentationMode.wrappedValue.dismiss()
}
Text("next")
}
}

Related

Navigation not working in SwiftUI - Pop to a specific view

I am trying to pop to a specific view using the isActive binding of NavigationLink of SwiftUI. Here is my code:
struct Root: View
{
#Environment(\.dismiss) var dismissValue: DismissAction
#State var rootToView1Navigattion: Bool = false
var body: some View
{
HStack
{
NavigationLink(isActive: $rootToView1Navigattion) {
View1()
} label: {
EmptyView()
}
Button {
dismissValue()
} label: {
Text("Go Back")
.font(.title3)
.padding()
.background(Color.yellow)
}
Button {
rootToView1Navigattion = true
} label: {
Text("Go Forward")
.font(.title3)
.padding()
.background(Color.green)
}
}
.navigationTitle(String(describing: Self.self))
}
}
struct View1: View
{
#Environment(\.dismiss) var dismissValue: DismissAction
#State var view1ToView2Navigation: Bool = false
var body: some View
{
HStack
{
NavigationLink(isActive: $view1ToView2Navigation) {
View2(view1ToView2NavBinding: $view1ToView2Navigation)
} label: {
EmptyView()
}
Button {
dismissValue()
} label: {
Text("Go Back")
.font(.title3)
.padding()
.background(Color.yellow)
}
Button {
view1ToView2Navigation = true
} label: {
Text("Go Forward")
.font(.title3)
.padding()
.background(Color.green)
}
}
.navigationTitle(String(describing: Self.self))
}
}
struct View2: View
{
#Binding var view1ToView2NavBinding: Bool
var body: some View
{
HStack
{
Button {
view1ToView2NavBinding = false
} label: {
Text("Go Back")
.font(.title3)
.padding()
.background(Color.yellow)
}
}
.navigationTitle(String(describing: Self.self))
}
}
As per my concept, View1 has a property view1ToView2Navigation, which is passed to isActive param of NavigationLink. And this property is passed as binding to View2. When I set it false in View2, everything above View1 should dismiss. But this is not working, Am I missing out something conceptually ??
My RootView is enclosed in a NavigationView. And When I do the same between Root and View1. It works smoothly.
Edited: Based on your new comment, you want to navigate back to root from view 2.
Try the below code. When you press the yellow back button in view2, you will be navigated back to root.
import SwiftUI
struct ContentView: View
{
#Environment(\.dismiss) var dismissValue: DismissAction
#State var toRoot = false
var body: some View
{
NavigationView
{
HStack {
Button {
dismissValue()
} label: {
Text("Go Back")
.font(.title3)
.padding()
.background(Color.yellow)
}
NavigationLink {
View1(toRoot: $toRoot)
} label: {
Text("Go Forward")
.font(.title3)
.padding()
.background(Color.green)
}
}
}
.navigationTitle(String(describing: Self.self))
}
}
struct View1: View
{
#Environment(\.dismiss) var dismissValue: DismissAction
#Binding var toRoot: Bool
var body: some View
{
HStack
{
Button {
dismissValue()
} label: {
Text("Go Back")
.font(.title3)
.padding()
.background(Color.yellow)
}
NavigationLink {
View2(toRoot: $toRoot)
} label: {
Text("Go Forward")
.font(.title3)
.padding()
.background(Color.green)
}
}
.navigationTitle(String(describing: Self.self))
.onAppear {
if toRoot {
dismissValue()
}
}
}
}
struct View2: View
{
#Binding var toRoot: Bool
#Environment(\.dismiss) var dismissValue: DismissAction
var body: some View
{
HStack
{
Button {
toRoot = true
dismissValue()
} label: {
Text("Go Back")
.font(.title3)
.padding()
.background(Color.yellow)
}
}
.navigationTitle(String(describing: Self.self))
}
}

Dismiss Tab View from a Child Navigation View and go back to RootView SwiftUI

My Navigation Flow:
Here, my View A to View G is under one Navigation View.
NavigationView {
ViewA()
}
And from View D & View G I am moving to my TabView H by modal, like this:
Button(action: {
isPresented.toggle()
}, label: {
Text("GO!")
})
.fullScreenCover(isPresented: $isPresented) {
TabbarView()
}
In my Tab View all the views have their own Navigation View, like this:
TabView(selection: $tabbarViewModel.tabSelection) {
NavigationView {
HomeView()
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Text("Home")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "house")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Home")
}
.tag(0)
NavigationView {
CartView()
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Text("Cart")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "cart")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Cart")
}
.tag(1)
NavigationView {
ProductView()
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Text("Product")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "seal")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Product")
}
.tag(2)
NavigationView {
ProfileView()
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Text("Profile")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "person")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Profile")
}
.tag(3)
}
.accentColor(Color("AppsDefaultColor"))
Now I want to go back to viewA, say from Home View by pressing the Sign Out button. I tried this, just to see if it takes me back to previous view, but it doesn't work.
struct HomeView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Dismiss")
})
}
}
So how can I dismiss the tabview and go back to my Root view A?
Finally I have managed to achieve this. To roll back to the Root view I used this:
NavigationLink(destination: <#T##_#>, tag: <#T##Hashable#>, selection: <#T##Binding<Hashable?>#>, label: <#T##() -> _#>)
And to dismiss the presented view, I used this:
UIApplication.shared.windows.first?.rootViewController?.dismiss(animated: true, completion: nil)
To be honest it's quite simple. All we need to make NavigationLink selection which is selectedItem in this case to nil & dismiss the modal throughout the project. All of these have done inside of a tab bar view model class with the help of #EnvironmentObject
First create the TabbarViewModel: ObservableObject:
import Foundation
import SwiftUI
class TabbarViewModel: ObservableObject {
#Published var tabSelection: Int = 0
#Published var selectedItem: String? = nil
func gotoRootView() {
withAnimation {
UIApplication.shared.windows.first?.rootViewController?.dismiss(animated: true, completion: nil)
selectedItem = nil
}
}
}
Now, let's create the ViewA which is AuthListView:
import SwiftUI
struct CellItem: Identifiable {
var id = UUID()
let title: String
let image: String
let destination: AnyView
}
struct AuthListView: View {
var body: some View {
AuthListContentView()
.navigationBarHidden(true)
}
}
struct AuthListContentView: View {
#State private var cellList: [CellItem] = [
CellItem(title: "Icon", image: "", destination: AnyView(EmptyView())),
CellItem(title: "Phone", image: "Phone", destination: AnyView(PhoneView())),
CellItem(title: "Email", image: "Email", destination: AnyView(SignInView())),
CellItem(title: "Google", image: "Google", destination: AnyView(GoogleView())),
CellItem(title: "Facebook", image: "Facebook", destination: AnyView(FacebookView())),
CellItem(title: "Twitter", image: "Twitter", destination: AnyView(TwitterView()))]
var body: some View {
List(cellList, id: \.id) { item in
if item.title == "Icon" {
IconImageView()
} else {
AuthListCell(cellItem: item)
}
}
}
}
struct IconImageView: View {
var body: some View {
VStack {
Image("firebase")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 120, height: 120, alignment: .center)
}
.frame(maxWidth: .infinity, minHeight: 120, alignment: .center)
.padding(.top, 50)
.padding(.bottom, 50)
}
}
This is each cell of auth View:
import SwiftUI
struct AuthListCell: View {
var cellItem: CellItem
#EnvironmentObject var tabbarViewModel: TabbarViewModel
var body: some View {
NavigationLink(
destination: cellItem.destination,
tag: cellItem.title,
selection: $tabbarViewModel.selectedItem) {
cell(cellItem: cellItem)
}
}
}
struct cell: View {
var cellItem: CellItem
var body: some View {
HStack(spacing: 15) {
Image(cellItem.image)
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 28, maxHeight: .infinity, alignment: .center)
Text(cellItem.title)
.foregroundColor(Color("DefaultText"))
.font(.system(size: 17))
Spacer()
}
.padding(.top, 5)
.padding(.bottom, 5)
}
}
Load this view inside of your ContentView under a Navigation View:
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
var body: some View {
NavigationView {
AuthListView()
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
Till now, we can push to ViewB from ViewA. Here, I am only showing the navigation flow for ViewA push> ViewB push> ViewC present> TabView > and then Dismiss TabView from HomeView and go back to root ViewA, cause rest of the other views will follow the same. It also works from a child navigation of any Tab bar views as well. So let's create ViewB(PhoneView) and push toViewC(PINView):
ViewB:
struct PhoneView: View {
var body: some View {
PhoneContentView()
.navigationBarTitle("Phone Number", displayMode: .inline)
}
}
struct PhoneContentView: View {
var body: some View {
NavigationLink(destination: PINView()) {
Text("Go")
}
}
}
ViewC:
struct PINView: View {
var body: some View {
PINContentView()
.navigationBarTitle("PIN", displayMode: .inline)
}
}
struct PINContentView: View {
#State private var isPresented = false
var body: some View {
Button(action: {
isPresented.toggle()
}, label: {
Text("Sign In")
})
.fullScreenCover(isPresented: $isPresented) {
TabbarView()
}
}
}
Till now we have presented the tab view from previous ViewC. This is our tab view:
import SwiftUI
struct TabbarView: View {
#EnvironmentObject var tabbarViewModel: TabbarViewModel
var body: some View {
TabView(selection: $tabbarViewModel.tabSelection) {
NavigationView {
HomeView().navigationBarTitle("Home", displayMode: .inline)
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "house")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Home")
}
.tag(0)
NavigationView {
CartView().navigationBarTitle("Cart", displayMode: .inline)
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "cart")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Cart")
}
.tag(1)
NavigationView {
ProductView().navigationBarTitle("Product", displayMode: .inline)
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image("product")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Product")
}
.tag(2)
NavigationView {
ProfileView().navigationBarTitle("Profile", displayMode: .inline)
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem {
Image(systemName: "person")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
Text("Profile")
}
.tag(3)
}
.accentColor(Color("AppsDefaultColor"))
}
}
Now, If I want to dismiss the presented tab view & go back to root view by pressing sign out button from my HomeView, all I have to do is call tabbarViewModel.gotoRootView() like this:
struct HomeView: View {
#EnvironmentObject var tabbarViewModel: TabbarViewModel
var body: some View {
Button(action: {
tabbarViewModel.gotoRootView()
}, label: {
Text("Sign Out")
})
}
}
I can dismiss the tab view and go to the root view from a child view of my HomeView as well. Let's go to a child view from the HomeView:
struct HomeView: View {
var body: some View {
NavigationLink(destination: HomeDetailsView()) {
Text("Go")
}
}
}
This is the HomedetailsView` and by following the same call I can accomplish the same result like this:
struct HomeDetailsView: View {
var body: some View {
HomeDetailsContentView()
.navigationBarTitle("Home Details", displayMode: .inline)
}
}
struct HomeDetailsContentView: View {
#EnvironmentObject var tabbarViewModel: TabbarViewModel
var body: some View {
Button(action: {
tabbarViewModel.gotoRootView()
}, label: {
Text("Dismiss")
})
}
}
By this you can dismiss tab view and go to the root view from any view of your project. :)

SwiftUI Hide TabView bar inside NavigationLink views

I have a TabView and separate NavigationView stacks for every Tab item. It works well but when I open any NavigationLink the TabView bar is still displayed. I'd like it to disappear whenever I click on any NavigationLink.
struct MainView: View {
#State private var tabSelection = 0
var body: some View {
TabView(selection: $tabSelection) {
FirstView()
.tabItem {
Text("1")
}
.tag(0)
SecondView()
.tabItem {
Text("2")
}
.tag(1)
}
}
}
struct FirstView: View {
var body: some View {
NavigationView {
NavigationLink(destination: FirstChildView()) { // How can I open FirstViewChild with the TabView bar hidden?
Text("Go to...")
}
.navigationBarTitle("FirstTitle", displayMode: .inline)
}
}
}
I found a solution to put a TabView inside a NavigationView, so then after I click on a NavigationLink the TabView bar is hidden. But this messes up NavigationBarTitles for Tab items.
struct MainView: View {
#State private var tabSelection = 0
var body: some View {
NavigationView {
TabView(selection: $tabSelection) {
...
}
}
}
}
struct FirstView: View {
var body: some View {
NavigationView {
NavigationLink(destination: FirstChildView()) {
Text("Go to...")
}
.navigationBarTitle("FirstTitle", displayMode: .inline) // This will not work now
}
}
}
With this solution the only way to have different NavigationTabBars per TabView item, is to use nested NavigationViews. Maybe there is a way to implement nested NavigationViews correctly? (As far as I know there should be only one NavigationView in Navigation hierarchy).
How can I hide TabView bar inside NavigationLink views correctly in SwiftUI?
I really enjoyed the solutions posted above, but I don't like the fact that the TabBar is not hiding according to the view transition.
In practice, when you swipe left to navigate back when using tabBar.isHidden, the result is not acceptable.
I decided to give up the native SwiftUI TabView and code my own.
The result is more beautiful in the UI:
Here is the code used to reach this result:
First, define some views:
struct FirstView: View {
var body: some View {
NavigationView {
VStack {
Text("First View")
.font(.headline)
}
.navigationTitle("First title")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.yellow)
}
}
}
struct SecondView: View {
var body: some View {
VStack {
NavigationLink(destination: ThirdView()) {
Text("Second View, tap to navigate")
.font(.headline)
}
}
.navigationTitle("Second title")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.orange)
}
}
struct ThirdView: View {
var body: some View {
VStack {
Text("Third View with tabBar hidden")
.font(.headline)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.red.edgesIgnoringSafeArea(.bottom))
}
}
Then, create the TabBarView (which will be the root view used in your app):
struct TabBarView: View {
enum Tab: Int {
case first, second
}
#State private var selectedTab = Tab.first
var body: some View {
VStack(spacing: 0) {
ZStack {
if selectedTab == .first {
FirstView()
}
else if selectedTab == .second {
NavigationView {
VStack(spacing: 0) {
SecondView()
tabBarView
}
}
}
}
.animation(nil)
if selectedTab != .second {
tabBarView
}
}
}
var tabBarView: some View {
VStack(spacing: 0) {
Divider()
HStack(spacing: 20) {
tabBarItem(.first, title: "First", icon: "hare", selectedIcon: "hare.fill")
tabBarItem(.second, title: "Second", icon: "tortoise", selectedIcon: "tortoise.fill")
}
.padding(.top, 8)
}
.frame(height: 50)
.background(Color.white.edgesIgnoringSafeArea(.all))
}
func tabBarItem(_ tab: Tab, title: String, icon: String, selectedIcon: String) -> some View {
ZStack(alignment: .topTrailing) {
VStack(spacing: 3) {
VStack {
Image(systemName: (selectedTab == tab ? selectedIcon : icon))
.font(.system(size: 24))
.foregroundColor(selectedTab == tab ? .primary : .black)
}
.frame(width: 55, height: 28)
Text(title)
.font(.system(size: 11))
.foregroundColor(selectedTab == tab ? .primary : .black)
}
}
.frame(width: 65, height: 42)
.onTapGesture {
selectedTab = tab
}
}
}
This solution also allows a lot of customization in the TabBar.
You can add some notifications badges, for example.
If we talk about standard TabView, the possible workaround solution can be based on TabBarAccessor from my answer on Programmatically detect Tab Bar or TabView height in SwiftUI
Here is a required modification in tab item holding NavigationView. Tested with Xcode 11.4 / iOS 13.4
struct FirstTabView: View {
#State private var tabBar: UITabBar! = nil
var body: some View {
NavigationView {
NavigationLink(destination:
FirstChildView()
.onAppear { self.tabBar.isHidden = true } // !!
.onDisappear { self.tabBar.isHidden = false } // !!
) {
Text("Go to...")
}
.navigationBarTitle("FirstTitle", displayMode: .inline)
}
.background(TabBarAccessor { tabbar in // << here !!
self.tabBar = tabbar
})
}
}
Note: or course if FirstTabView should be reusable and can be instantiated standalone, then tabBar property inside should be made optional and handle ansbsent tabBar explicitly.
Thanks to another Asperi's answer I was able to find a solution which does not break animations and looks natural.
struct ContentView: View {
#State private var tabSelection = 1
var body: some View {
NavigationView {
TabView(selection: $tabSelection) {
FirstView()
.tabItem {
Text("1")
}
.tag(1)
SecondView()
.tabItem {
Text("2")
}
.tag(2)
}
// global, for all child views
.navigationBarTitle(Text(navigationBarTitle), displayMode: .inline)
.navigationBarHidden(navigationBarHidden)
.navigationBarItems(leading: navigationBarLeadingItems, trailing: navigationBarTrailingItems)
}
}
}
struct FirstView: View {
var body: some View {
NavigationLink(destination: Text("Some detail link")) {
Text("Go to...")
}
}
}
struct SecondView: View {
var body: some View {
Text("We are in the SecondView")
}
}
Compute navigationBarTitle and navigationBarItems dynamically:
private extension ContentView {
var navigationBarTitle: String {
tabSelection == 1 ? "FirstView" : "SecondView"
}
var navigationBarHidden: Bool {
tabSelection == 3
}
#ViewBuilder
var navigationBarLeadingItems: some View {
if tabSelection == 1 {
Text("+")
}
}
#ViewBuilder
var navigationBarTrailingItems: some View {
if tabSelection == 1 {
Text("-")
}
}
}
How about,
struct TabSelectionView: View {
#State private var currentTab: Tab = .Scan
private enum Tab: String {
case Scan, Validate, Settings
}
var body: some View {
TabView(selection: $currentTab){
ScanView()
.tabItem {
Label(Tab.Scan.rawValue, systemImage: "square.and.pencil")
}
.tag(Tab.Scan)
ValidateView()
.tabItem {
Label(Tab.Validate.rawValue, systemImage: "list.dash")
}
.tag(Tab.Validate)
SettingsView()
.tabItem {
Label(Tab.Settings.rawValue, systemImage: "list.dash")
}
.tag(Tab.Settings)
}
.navigationBarTitle(Text(currentTab.rawValue), displayMode: .inline)
}
}
I also faced this problem. I don't want to rewrite, but the solution is in my github. I wrote everything in detail there
https://github.com/BrotskyS/AdvancedNavigationWithTabView
P.S: I have no reputation to write comments. Hikeland's solution is not bad. But you do not save the State of the page. If you have a ScrollView, it will reset to zero every time when you change tab
Also you can create very similar custom navBar for views in TabView
struct CustomNavBarView<Content>: View where Content: View {
var title: String = ""
let content: Content
init(title: String, #ViewBuilder content: () -> Content) {
self.title = title
self.content = content()
}
var body: some View {
content
.safeAreaInset(edge: .top, content: {
HStack{
Spacer()
Text(title)
.fontWeight(.semibold)
Spacer()
}
.padding(.bottom, 10)
.frame(height: 40)
.frame(maxWidth: .infinity)
.background(.ultraThinMaterial)
.overlay {
Divider()
.frame(maxHeight: .infinity, alignment: .bottom)
}
})
}
}
CustomNavBarView(title: "Create ad"){
ZStack{
NavigationLink(destination: SetPinMapView(currentRegion: $vm.region, region: vm.region), isActive: $vm.showFullMap) {
Color.clear
}
Color("Background").ignoresSafeArea()
content
}
}

Dismiss modal view from another view in swiftUI

I want to dismiss a modal view in SwiftUI, but I just can't
This is my code:
ContentView:
import SwiftUI
enum Destination {
case modal
}
struct ContentView: View {
#Environment(\.presentationMode) var presentationMode
#State private var showModal = false
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
VStack{
SubscribeButtonView(buttonTitle: "Modal", destination: .modal, showModal: $showModal)
.padding(.top, 50)
.padding(.leading, 30)
.padding(.trailing, 30)
Spacer()
}
}
.navigationBarTitle(Text("Menu"))
}
}
}
The ModalView:
import SwiftUI
struct ModalView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading) {
Spacer()
SaveButtonView(origin: .modal)
Spacer()
}
.padding(.leading, 20)
.padding(.trailing, 20)
}
.navigationBarTitle("Modal")
.navigationBarItems(trailing: Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Cancel")
})
}
}
}
The SubscribeButtonView:
import SwiftUI
struct SubscribeButtonView: View {
#Environment(\.presentationMode) var presentationMode
var buttonTitle: String
var destination: Destination
#Binding var showModal: Bool
var body: some View {
Button(action: {
self.showModal.toggle()
}) {
HStack {
Image(systemName: "plus.circle")
.font(.body)
Text(buttonTitle)
}
}.sheet(isPresented: $showModal) {
if self.destination == .modal {
ModalView()
}
}
.padding()
}
}
The SaveButtonView:
import SwiftUI
struct SaveButtonView: View {
#Environment(\.presentationMode) var presentationMode
var origin: Destination
var body: some View {
Button(action: {
//THIS IS NOT WORKING
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Text("Save")
}
}
.padding()
}
}
I tried to create a really simple new project with just one state to call the modal, the menu, the modal and the two buttons and it worked just fine. I can't understand why it isn't working in the code above
Did anyone have this same issue?
You are supposed to bind the presentationMode from ModalView.
struct SaveButtonView: View {
//#Environment(\.presentationMode) var presentationMode
#Binding var presentationMode : PresentationMode
var origin: Destination
var body: some View {
Button(action: {
//THIS IS NOT WORKING
self.$presentationMode.wrappedValue.dismiss()
}) {
HStack {
Text("Save")
}
}
.padding()
}
}
struct ModalView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading) {
Spacer()
SaveButtonView( presentationMode: self.presentationMode, origin: .modal)
Spacer()
}
.padding(.leading, 20)
.padding(.trailing, 20)
}
.navigationBarTitle("Modal")
.navigationBarItems(trailing: Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Cancel")
})
}
}
}

SwiftUI How to push to next screen when tapping on Button

I can navigate to next screen by using NavigationButton (push) or present with PresentationButton (present) but i want to push when i tap on Buttton()
Button(action: {
// move to next screen
}) {
Text("See More")
}
is there a way to do it?
You can do using NavigationLink
Note: Please try in real device. in simulator sometimes not work properly.
struct MasterView: View {
#State var selection: Int? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailsView(), tag: 1, selection: $selection) {
Button("Press me") {
self.selection = 1
}
}
}
}
}
}
struct DetailsView: View {
#Environment(\.presentationMode) var presentation
var body: some View {
Group {
Button("Go Back") {
self.presentation.wrappedValue.dismiss()
}
}
}
}
As you can see to display the new view, add the NavigationLink with isActive: $pushView using <.hidden()> to hide the navigation "arrow".
Next add Text("See More") with tapGesture to make the text respond to taps. The variable pushView will change (false => true) when you click "See More" text.
import SwiftUI
struct ContentView: View {
#State var pushView = false
var body: some View {
NavigationView {
List {
HStack{
Text("test")
Spacer()
NavigationLink(destination: NewView(), isActive: $pushView) {
Text("")
}.hidden()
.navigationBarTitle(self.pushView ? "New view" : "default view")
Text("See More")
.padding(.trailing)
.foregroundColor(Color.blue)
.onTapGesture {
self.pushView.toggle()
}
}
}
}
}
}
struct NewView: View {
var body: some View {
Text("New View")
}
}
ContentView picture
NewView picture
To tap on button and navigate to next screen,You can use NavigationLink like below
NavigationView{
NavigationLink(destination: SecondView()) {
Text("Login")
.padding(.all, 5)
.frame(minWidth: 0, maxWidth: .infinity,maxHeight: 45, alignment: .center)
.foregroundColor(Color.white)
}
}
You can use NavigationLink to implement this:
struct DetailsView: View {
var body: some View {
VStack {
Text("Hello world")
}
}
}
struct ContentView: View {
#State var selection: Int? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailsView(), tag: 1, selection: $selection) {
Button("Press me") {
self.selection = 1
}
}
}
}
}
}

Resources