iosswiftuiswiftui-navigationstack

NavigationStack inconsistent push animation with zoom navigation transition when back button hidden


In SwiftUI with attached code when i set navigationBarBackButtonHidden to true, the NavigationStack's navigation bar always pushes in when I dismiss the detail view. Since it's a zoom transition, I want the navigation bar to stay and not animate. How do i solve this?

import SwiftUI

struct ContentView: View {
    @Namespace private var namespace
    var body: some View {
        NavigationStack {
            NavigationLink {
                DetailView()
                    // here is the issue: if I set it to true, it will have that default push animation when dismissed, which is inconsistent with the zoom animation.
                    .navigationBarBackButtonHidden(false)
                    .navigationTransition(.zoom(sourceID: "world", in: namespace))
            } label: {
                Image(systemName: "globe")
                    .matchedTransitionSource(id: "world", in: namespace)
            }
            .navigationTitle("Main")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .primaryAction){
                    Button {
                        print("noop")
                    } label: {
                        Label("Toggle", systemImage: "rectangle.grid.1x2")
                    }
                }
            }
        }
    }
}

struct DetailView: View {
    var body: some View {
        ZStack{
            Color.indigo
            Text("body here")
        }.ignoresSafeArea()
    }
}

#Preview {
    ContentView()
}

inconsistent push navigation bar


Solution

  • The animation seems to be associated with the navigation bar being removed, because there is no content.

    The workaround you showed in your answer is retaining some content, so the navigation bar is not removed.

    You could also try setting a navigation title:

    DetailView()
        .navigationBarBackButtonHidden(true)
        .navigationTransition(.zoom(sourceID: "world", in: namespace))
        .navigationTitle("Detail") // ๐Ÿ‘ˆ here
        .navigationBarTitleDisplayMode(.inline) // ๐Ÿ‘ˆ and here
    

    If you don't want to set a title then you can force the navigation bar to remain visible anyway, by applying .toolbarVisibility(.visible, for: .navigationBar).

    DetailView()
        .navigationBarBackButtonHidden(true)
        .navigationTransition(.zoom(sourceID: "world", in: namespace))
        .navigationBarTitleDisplayMode(.inline)
        .toolbarVisibility(.visible, for: .navigationBar)
        .toolbarBackgroundVisibility(.hidden, for: .navigationBar)
    

    EDIT Following up on your comment:

    The goal is to not have any extra vertical space taken. This solution even with inline title still takes some good vertical space... are there better solutions?

    If a state variable is used to control the visibility of the navigation bar then it can be hidden when navigating to the detail view, which means it no longer uses any space:

    @State private var navBarVisibility = Visibility.visible
    
    NavigationStack {
        NavigationLink {
            DetailView()
                .navigationBarBackButtonHidden(true)
                .navigationTransition(.zoom(sourceID: "world", in: namespace))
                .navigationBarTitleDisplayMode(.inline)
                .toolbarVisibility(navBarVisibility, for: .navigationBar)
                .onAppear { navBarVisibility = .hidden }
                .onDisappear { navBarVisibility = .visible }
        } label: {
            Image(systemName: "globe")
                .matchedTransitionSource(id: "world", in: namespace)
        }
        .onAppear { navBarVisibility = .visible }
        .onDisappear { navBarVisibility = .hidden }
        .navigationTitle("Main")
        .navigationBarTitleDisplayMode(.inline)
        .toolbarVisibility(navBarVisibility, for: .navigationBar)
        .toolbar {
            // ... as before
        }
    }
    

    Animation