GO FURTHER, FASTER: Try the Swift Career Accelerator today! >>

SOLVED: Animations with programmable navigation

Forums > SwiftUI

I am writing an anki-like app with language exercises. Completing an exercise leads you to the exercise with the next word, and so on, indefinitely. I want to transition between words using navigation. This is a simplified example of what I have come up with:

struct ContentView: View {
    @State private var page: Int = 0
    @State private var path = NavigationPath()

    var body: some View {
        NavigationStack(path: $path) {
            VStack {
                NavigationLink("Go to page \(page)", value: page)
            }
            .navigationDestination(for: Int.self) { currentPage in
                VStack {
                    Text("You selected \(currentPage)")
                    Button("Next page") {
                        page += 1
                        path.removeLast()
                        path.append(page)
                    }
                }.onAppear() {
                    print("View with page \(page) is appearing")
                }
            }
        }
    }
}

I use NavigationPath with pushing and popping to "replace" the uppermost view with the next word exercise. Note that I don't want to just "push" the view into the stack, because then it will grow indefinitely. I also want the "back" button to lead to the main menu, not the previous exercise. The code provided works, but there is no animation when transitioning to next page. Also, the "onAppear" method is not called when navigating to the next page.

Suspecting that it has to do with view hierarchy not changing, I've tried make id of the VStack view depend on the page using .id modifier, and also pushing different types of objects into path - none of these helped.

Would be really grateful for any ideas on how to fix this!

   

Hi!

Not sure if you're trying to acheive this effect. As it will basically move back to the root screen and then add another on top of it with new navigation.

struct ContentView: View {
    @State private var page: Int = 0
    @State private var path = [Int]()

    var body: some View {
        NavigationStack(path: $path) {
            VStack {
                NavigationLink("Go to page \(page)", value: page)
            }
            .navigationDestination(for: Int.self) { currentPage in
                VStack {
                    Text("You selected \(currentPage)")
                    Button("Next page") {
                        page += 1
                        path.append(page)
                    }
                }
            }
        }
        .onChange(of: page) { _, _ in
            path.remove(at: 0)
        }
    }
}

1      

Another similar question: https://stackoverflow.com/questions/77103126/unexpected-transitions-when-i-add-and-remove-screens-from-navigationstack-s-path So my expectations were correct - NavigationPath will not trigger any animations if total stack height remains the same. The only way around it is to trick it with pushes and pops with some delay.

struct ContentView: View {
    @State private var page: Int = 0
    @State private var path = [Int]()

    var body: some View {
        NavigationStack(path: $path) {
            VStack {
                NavigationLink("Go to page \(page)", value: page)
            }//.navigationTransition(.slide2(axis: .horizontal).animation(.easeInOut(duration: 0.1)))
            .navigationDestination(for: Int.self) { currentPage in
                VStack {
                    Text("You selected \(currentPage)")
                    Button("Next page") {
                        page += 1
                        // this hack
                        path.append(page)
                        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) {
                            // remove second-to-last element from navstack to only have one page on it
                            UIView.setAnimationsEnabled(false)
                            path.removeLast(2)
                            path.append(page)
                            // enable animations back!
                            DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) {
                                UIView.setAnimationsEnabled(true)
                            }
                        }
                    }.onAppear {
                        print("Page \(page) is loaded")
                    }
                }//.navigationTransition(.slide2(axis: .horizontal, inverted: false).animation(.easeInOut(duration: 0.1)))
            }
        }
    }
}

This solution so far looks the smoothest in terms of transitions

   

Hacking with Swift is sponsored by Essential Developer.

SPONSORED Transform your career with the iOS Lead Essentials. This Black Friday, unlock over 40 hours of expert training, mentorship, and community support to secure your place among the best devs. Click for early access to this limited offer and a free crash course.

Save your spot

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.