Cross navigation with NavigationView - swiftui

I have the following structure
enum Page {
case chapter1
case chapter2
}
struct ContentView: View {
#State var page: Page? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Chapter1(page: self.$page),
tag: .chapter1,
selection: self.$page) {
Text("Chapter 1")
}
NavigationLink(destination: Chapter2(page: self.$page),
tag: .chapter2,
selection: self.$page) {
Text("Chapter 2")
}
}
}
}
}
struct Chapter1: View {
#Binding var page: Page?
var body: some View {
VStack {
Button("Back to Overview") {
self.page = nil
}
Button("Chapter 2") {
self.page = .chapter2
}
}
}
}
struct Chapter2: View {
#Binding var page: Page?
var body: some View {
VStack {
Button("Back to Overview") {
self.page = nil
}
}
}
}
The moment I go from Chapter1 directly to Chapter2 it breaks the backlink in Chaper2.
Firstly, what is the reason for that?
Secondly, is there a way to transition from Chapter1 to Chapter2 with a different kind of animation, instead of duplicate back-and-forth flip?

The NavigationView manger links on per-level base, so modifying other stack level can result it unexpected behaviour.
Here is a solution for the described use-case. Tested with Xcode 11.4 / iOS 13.4
struct TestNavigationByEnum: View {
#State var page: Page? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Chapter1(page: self.$page),
tag: .chapter1,
selection: self.$page) {
Text("Chapter 1")
}.isDetailLink(false) // important !!
NavigationLink(destination: Chapter2(page: self.$page),
tag: .chapter2,
selection: self.$page) {
Text("Chapter 2")
}.isDetailLink(false)
}
}
}
}
struct Chapter1: View {
#Binding var page: Page?
var body: some View {
VStack {
Button("Back to Overview") {
self.page = nil
}
// own level link
NavigationLink(destination: Chapter2(page: self.$page)) {
Text("Chapter 2")
}.isDetailLink(false)
}
}
}

Related

How hide navigation bar always back from any view directly using NavigationView?

I am using xcode-14.2 & minimum target version 14. I have three views ContentView, Welcome & `FundTransfer. Here is my case.
ContentView - Load first view & navigationBarHidden is working. When Welcome page button click it goes to Welcome page
Welcome view - When Fund Transfer button is clicked, it goes to FundTransfer view
FundTransfer - when Log out button is clicked, it goes to ContentView
It goeslike: ContentView-> FundTransfer-> ContentView
Problem: When it goes from FundTransfer view to ContentView it shows navigationBar. That means when back from FundTransfer view to ContentView shows navigationBar which was hidden at the first.
How do I hide navigation bar always back from any view directly to ContentView?
Here is my code:
ContentView:
struct ContentView: View {
#State private var showWelcome = false
#State var isNavigationBarHidden: Bool = true
var body: some View {
NavigationView {
VStack {
ScrollView {
VStack(alignment: .customCenter,spacing: 0){
VStack {
SubmitButton(action: {
self.showWelcome = true
}) {
Text("Welcome page")
}
}
NavigationLink(destination: Welcome(), isActive: $showWelcome) { EmptyView() }
}
}
}
.navigationBarTitle("") //this must be empty
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
}
Welcome View:
struct Welcome: View {
#State private var showFundTransfer = false
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
ScrollView {
VStack(alignment: .customCenter,spacing: 0){
VStack {
SubmitButton(action: {
showFundTransfer = true
}) {
Text("Fund Transfer")
}
}
NavigationLink(destination: FundTransfer(), isActive: $showFundTransfer) { EmptyView() }
}
}
.navigationBarHidden(true)
}
}
}
FundTransfer View:
struct FundTransfer: View {
#State var isNavigationBarHidden: Bool = true
#State private var logon = false
var body: some View {
VStack {
ScrollView {
VStack(alignment: .customCenter,spacing: 0){
SubmitButton(action: {
self.logon = true
}) {
Text("Log out")
}
}
}
NavigationLink(destination: ApplicationSwitcher(), isActive: $logon) { EmptyView() }.opacity(0)
}
.navigationBarHidden(true)
}
}
Please help me..
Add .navigationBarHidden(true) in NavigationLink also for eg:
NavigationLink(destination: ApplicationSwitcher()
.navigationBarHidden(true), isActive: $logon) { EmptyView() }.opacity(0)
In ContentView add "navigationBarHidden(true)" after the closure of NavigationView instead of VStack as mentioned below:
NavigationView {
...
}.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)

How do I use a NavigationLink in SwiftUI?

I am new to Xcode and work on a Login Page.
I try to link all pages with each other but even if I saw these Code in a tutorial working it doesn't work for me.
I hope you can help me, I would appreciate it a lot?
In NavigationLink the order of parameters is very important
here is an example that may help you
struct Page1: View {
#Binding var GoToPage2: Bool
var body: some View {
VStack{
Button("Go To Page"){
GoToPage2 = true
print(GoToPage2)
}
NavigationLink(destination: Page2(), isActive: $GoToPage2){
EmptyView()
}
}
.navigationTitle("Page 1")
}
}
struct Page2: View {
var body: some View {
Text("page 2!")
.padding()
.navigationTitle("Page 2")
}
}
struct ContentView: View {
#State var GoToPage2 = false
var body: some View {
NavigationView{
VStack{
NavigationLink(destination: Page1(GoToPage2: $GoToPage2)){
Text("Signe In")
.padding()
.background(.regularMaterial)
.colorScheme(.dark)
.cornerRadius(12)
.font(.largeTitle.bold())
.foregroundColor(.primary)
}
}
.navigationTitle("App")
}
}
}
You can use NavigationLink in many different ways. Documentation here
struct ContentView: View {
#State var active: Bool = false
var body: some View {
NavigationView {
List {
NavigationLink(destination: EmptyView()) {
Label("Badge", systemImage: "app.badge")
}
NavigationLink("General", destination: EmptyView())
NavigationLink("About", destination: EmptyView(), isActive: $active)
}
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("View1")
// put the second view in destination in which you want to reach
// click in view2, this code will redirect from view1 to view2
NavigationLink(destination: View2()){
Text("View2")
}
}
}
}
}

iOS15 NavigationView lost the state after putting app in background and bring it back

In the app, if I follow the navigations from MainView->Tab1->Link1->Sub Link1, put the app to background, then bring it back, then it shows back to Tab1 again, does anyone know why NavigationView cannot keep the last view? it works fine in iOS 14.7
struct LocalNotificationDemoView: View {
#StateObject var localNotification = LocalNotification()
#ObservedObject var notificationCenter: NotificationCenter
var body: some View {
NavigationView {
VStack {
MainView()
}
}
.navigationViewStyle(.stack)
}
}
struct MainView: View {
var body: some View {
TabView {
Tab1()
.tabItem {
Text("Tab1")
}
Tab2()
.tabItem {
Text("Tab2")
}
}
}
}
struct Tab1: View {
#State var selection: Int? = nil
var body: some View {
NavigationLink(destination: View1(), tag: 1, selection: $selection) {
Text("Link 1")
}
}
}
struct Tab2: View {
#State var selection: Int? = nil
var body: some View {
NavigationLink(destination: Text("Link 2").navigationTitle("").navigationBarHidden(true), tag: 1, selection: $selection) {
Text("Link 2")
.onAppear {
print("Link2 shows")
Thread.callStackSymbols.forEach{print($0)}
}
}
}
}
struct View1: View {
#State var selection: Int? = nil
var body: some View {
NavigationLink(destination: Text("Sub Link 1"), tag: 1, selection: $selection) {
Text("Sub Link 1")
}
}
}

NavigationView always creates a new view

I created a NavigationView and it works, but everytime I click on a NavigationLink, SwiftUI recreates the destination view and resets every property in it. See this example:
struct NavView: View {
#State var selectionIndex: Int? = nil
let views = [
DestinationView(viewNumber: 1),
DestinationView(viewNumber: 2),
DestinationView(viewNumber: 3)
]
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: self.views[0], tag: 0, selection: $selectionIndex) {
Text("View 1")
}
NavigationLink(destination: self.views[1], tag: 1, selection: $selectionIndex) {
Text("View 2")
}
NavigationLink(destination: self.views[2], tag: 0, selection: $selectionIndex) {
Text("View 3")
}
}
self.views[0]
}
}
}
struct DestinationView: View {
#State var viewNumber: Int
#State var count = 0
var body: some View {
VStack {
Text("View \(self.viewNumber)")
.font(.largeTitle)
Text("Count: \(self.count)")
Button(action: {
self.count += 1
}) {
Text("Increase counter")
}
}
}
}
In this example clicking for example on the View1 link opens the View 1. Pressing the button increases the count property. If you then click on the View2 button and then back the View1 button, the count property will be zero again.
EDIT
I also tried not to store the views and create them directly in body, as suggested in the comments. Doesn't work either :(
struct NavView: View {
#State var selectionIndex: Int? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DestinationView(viewNumber: 1), tag: 0, selection: $selectionIndex) {
Text("View 1")
}
NavigationLink(destination: DestinationView(viewNumber: 2), tag: 1, selection: $selectionIndex) {
Text("View 2")
}
NavigationLink(destination: DestinationView(viewNumber: 3), tag: 2, selection: $selectionIndex) {
Text("View 3")
}
}
}
}
}
EDIT 2
I also tried to store the views globally, I also tried with and without tag: and selection:
Issue in your code is that every time you are by default initializing count to 0 whenever we are navigating to DestinationView so trick is we have to update count value and save it some where in NavView.
First of all you have to create DestinationViewModel to keep track of your updated count values like below
class DestinationViewModel: ObservableObject {
#Published var viewNumber: Int
#Published var count: Int
init(_ viewNumber: Int, count: Int) {
self.viewNumber = viewNumber
self.count = count
}
}
Then you can access your DestinationViewModel in your DestinationView
struct DestinationView: View {
#EnvironmentObject var viewModel: DestinationViewModel
var body: some View {
VStack {
Text("View \(viewModel.viewNumber)")
.font(.largeTitle)
Text("Count: \(viewModel.count)")
Button(action: {
self.viewModel.count += 1
}) {
Text("Increase counter")
}
}
}
}
Below is your updated NavView
class DestinationViewModelContainer: ObservableObject {
#Published var viewModels: [DestinationViewModel]
init(_ viewModels: [DestinationViewModel]) {
self.viewModels = viewModels
}
}
struct NavView: View {
#EnvironmentObject var viewModelContainer: DestinationViewModelContainer
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DestinationView().environmentObject(viewModelContainer.viewModels[0])) {
Text("View 1")
}
NavigationLink(destination: DestinationView().environmentObject(viewModelContainer.viewModels[1])) {
Text("View 2")
}
NavigationLink(destination: DestinationView().environmentObject(viewModelContainer.viewModels[2])) {
Text("View 3")
}
}
}
}
}
and Here you can call your NavView from below code
let contentView = NavView().environmentObject(DestinationViewModelContainer([DestinationViewModel(1, count: 0), DestinationViewModel(2, count: 0),DestinationViewModel(3, count: 0)]))
Hope it will help you.

Multi Level Navigation SwiftUI

I have a master detail application I'm working on in SwiftUI but once on the 1st DetailView, NavigationLink in the NavBar no longer works. I wrote this as a simple demonstration:
struct NavView: View {
var body: some View {
NavigationView {
NavigationLink(destination: Layer()) {Text("Go to Layer 1")}
}
}
}
struct Layer: View {
var body: some View {
Text("Welcome to Layer 1")
.navigationBarItems(trailing: NavigationLink(destination: AnotherLayer()) { Text("Go to Layer 2") })
}
}
struct AnotherLayer: View {
var body: some View {
Text("Welcome to Layer 2")
}
}
Everything renders and you can tap the navigationBarItem in Layer but nothing happens.
What's going on here? How can I access AnotherLayer?
A NavigationLink used as a navigationBarItem will not work no matter if you use it in the first or second level but a NavigationDestinationLink would solve the problem.
import SwiftUI
struct TestSwift: View {
var body: some View {
NavigationView {
NavigationLink(destination: Layer()) {Text("Go to Level")}
}
}
}
struct Layer: View {
let detailView = NavigationDestinationLink(AnotherLayer())
var body: some View {
VStack {
Text("Text")
}
.navigationBarItems(trailing:
Button(action: {
self.detailView.presented?.value = true
}, label: {
Text("Present Now!")
})
)
}
}
struct AnotherLayer: View {
var body: some View {
Text("Hello")
}
}