iosswiftnavigationkeyboard

SwiftUI NavigationStack causes jump when navigating back with keyboard up


I have a NavigationStack with some padding on the bottom. When navigating to a detail view that contains a TextField, the keyboard slides up as expected. However, if I go back while the keyboard is still up, the previous view jumps up and down.

Here's a minimal reproducible example:

NavigationStack {
  NavigationLink {
    TextField("Placeholder", text: $text)
  } label: {
    Text("Click to open detail")
        .padding()
        .background(.blue.opacity(0.2))
  }
}.padding(.bottom, 50) // To leave space for a custom tab bar

And here it is visualised:

Visualisation

I tried using .ignoresSafeArea(.keyboard) on both the root view, the destination view, and the navigation stack itself, but nothing seems to fully fix the issue. The only slight improvement is when applied to the NavigationStack, but the jump still happens.

Jump still happens

In my full app, I'm implementing a custom tab bar with a predefined height, and I want the NavigationStack to be inset so its content doesn't go below the tab bar (as the NavigationStack is presented in an overlay).

How can I prevent this jump when navigating back with the keyboard still up?

Note: happens even on the latest iOS 18.


Solution

  • I figured it out. I guess that NavigationStack doesn't like padding or being in a VStack. So I thought I needed to apply the padding on all the inner views but that is repetitive and a code smell. The solution is to modify the safe area.

    I used the Introspect library to modify the additionalSafeAreaInsets on the navigation stack itself:

    .introspect(.navigationStack, on: .iOS(.v16, .v17, .v18)) { stack in
        stack.additionalSafeAreaInsets.bottom = 50
    }
    

    This 'moves' all the inner views up by 50 because they are respecting the safe area, while the NavigationStack is fullscreen so no broken animation jumps occur.

    In iOS 17+ we have safeAreaPadding(_:) which might do the same (didn't test it) without relying on a third-party library but this solution works when you're targeting iOS 16.