BLACK FRIDAY: Save 50% on all my Swift books and bundles! >>

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

   

Save 50% in my WWDC sale.

SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

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.