Search code examples
swiftswiftuiswiftui-navigationlink

Embedded navigationlinks


I am trying to develop an app to do a little cognitive testing

When opening the app there is a NavigationView{} with NavigationLink{}

So far so normal One of the links NavigationLink(destination: IntermediateCoreView()) { Text("Go to tests") }

Takes you to a 'prescreen for the tests, from which you can link to the 'test' its self From the IntermediateCoreView.swift you can NavigationLink(destination: ContentView()) { Text("+ new test") }

Which works, you can take the test. At the end of the test (X seconds pass) and then it displays an alert that the user has run out of time and takes them back to the Intermediate CoreView

This is where it goes wrong as the IntermediateCoreView, 'Back' button in the NavigationView goes back to the 'test' view. Not back to the InitialView

I get that this is 'expected behaviour', the test is back from the screen its been sent to. Is there a way to override this?

To add a minimal example - the .app file:

import SwiftUI

@main
struct minRep2App: App {
    var body: some Scene {
        WindowGroup {
            initialView()
        }
    }
}

Then the initial view controller:

import SwiftUI

struct initialView: View {
    var body: some View {
        NavigationView{
            List{
                NavigationLink(destination: ContentView()) {
                       Text("Go to CoRe tests")
                   }
            }
        }
      
    }
}
struct initialView_Previews: PreviewProvider {
    static var previews: some View {
        initialView()
    }
}

Lastly the test demo

import SwiftUI

struct ContentView: View {
    @State private var timeRemaining = 50.00
    @State private var showingAlert = false
    @State var showIntermediate: Bool = false
    let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
    var body: some View {
        NavigationLink(destination: initialView(), isActive: self.$showIntermediate)
        {
            EmptyView()
        }
        Text("Test goes here")
            .padding()
        HStack{
            //  Text("Score: \(score)")
            Text("Time: \(timeRemaining)")
        }.padding(.bottom, 10)
        .onReceive(timer) { time in
            if self.timeRemaining > 0.1 {
                self.timeRemaining -= 1
            }
            if  self.timeRemaining == 0.0 {
                self.showingAlert = true
                self.timer.upstream.connect().cancel()
            }
        }
        .alert(isPresented: $showingAlert){
            Alert(title: Text("Warning"), message: Text("Sorry your time is up!"), dismissButton: .default(Text("OK"), action: {
                self.showIntermediate = true
            })
            )
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Solution

  • Change your ContentView to this:

    import SwiftUI
    
    struct ContentView: View {
        
        @Environment(\.presentationMode) var presentationMode
    
        @State private var timeRemaining = 50.00
        @State private var showingAlert = false
    
        let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
        var body: some View {
            Text("Test goes here")
                .padding()
            HStack{
                //  Text("Score: \(score)")
                Text("Time: \(timeRemaining)")
            }.padding(.bottom, 10)
            .onReceive(timer) { time in
                if self.timeRemaining > 0.1 {
                    self.timeRemaining -= 1
                }
                if  self.timeRemaining == 0.0 {
                    self.showingAlert = true
                    self.timer.upstream.connect().cancel()
                }
            }
            .alert(isPresented: $showingAlert){
                Alert(title: Text("Warning"), message: Text("Sorry your time is up!"), dismissButton: .default(Text("OK"), action: {
                    self.presentationMode.wrappedValue.dismiss()
                })
                )
            }
        }
    }
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    This has nothing to do with the back button which will work fine. The problem is the logic in your alert caused you to instantiate a new, wholly different, InitialView. Think of it like webpages. If you navigate from one to the next, and then click on a link that goes to the first one, you have actually gone FORWARD to the first page, and if you click the back button you will get to the second page. Same thing here.

    Instead, you should use @Environment(\.presentationMode) var presentationMode which, when you call self.presentationMode.wrappedValue.dismiss() will trigger you to go back to the view that presented the view that you are trying to navigate back from.