SwiftUI 4 navigation bug on iOS 16?

Hi everyone,

I am running into a navigation-related issue that appears to be new in iOS 16/SwiftUI 4. The code below illustrates the problem:

struct ContentView: View {
  @State private var input: String = ""
  var body: some View {
    NavigationStack {
      VStack {
        Spacer()
        NavigationLink("Navigate") {
          Text("Nested View")
        }
        Spacer()
        TextEditor(text: $input)
          .border(.red)
          .frame(height: 40)
      }
      .padding()
    }
  }
}

The initial view consists of a navigation link and a text editor at the bottom of the screen. I run the app on an iPhone (or the simulator) in iOS 16 and click on the text editor at the bottom of the screen. This invokes the virtual keyboard, which pushes up the text field. Next, I click on "Navigate" to navigate to the nested view. I navigate immediately back, which brings back the initial view without the keyboard. Unfortunately, the text field isn't pushed back to the bottom and is now located in the middle of the screen. (see attached image)

Did anyone experience similar issues? Is there a workaround I could use to force a re-layout of the initial view upon return from the navigation link?

Thanks,
Matthias

Post not yet marked as solved Up vote post of ObjectHub Down vote post of ObjectHub
2.5k views
  • Just wanted to add that I experience the same problem if I compile equivalent code on Xcode 13 and run it on iOS 16. The same binary runs flawlessly on iOS 15.

  • having the same issue :/

  • Did you find any workable solution?

Add a Comment

Replies

Maybe you need to resign first responder on the text field when you click the "Navigate" link so the keyboard disappears. That might make the text field drop down to its original position (just a guess).

Alternatively, you could make the text field first responder whenever the initial view is displayed (so, when running the app, or coming back from the nested view) if it was being edited when you tapped "Navigate".

This one will work:

You need to use NavigationLink with an activation parameter so that you can track the start of navigation. When switching the navigation link activation parameter, force the keyboard to be removed

extension UIApplication: UIGestureRecognizerDelegate {
    func dismissKeyboard() {
         sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

Button {
    UIApplication.shared.dismissKeyboard()
    activateLink = true
} label: {
    Text("Activate link")
}
.background(
    NavigationLink(destination: DestinationView(), isActive: $activateLink) { EmptyView()}
)

In iOS 15, there are no such problems.

Can't believe this is still not fixed...

Here's a fix, With the help from https://stackoverflow.com/a/60178361/3405069

import SwiftUI
import Combine

struct AdaptsToKeyboard: ViewModifier {
    @State var currentHeight: CGFloat = 0
    
    func body(content: Content) -> some View {
        GeometryReader { geometry in
            content
                .padding(.bottom, self.currentHeight)
                .animation(.easeOut(duration: 0.5), value: currentHeight)
                .onAppear(perform: {
                    NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillShowNotification)
                        .merge(with: NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillChangeFrameNotification))
                        .compactMap { notification in
                            withAnimation(.easeOut(duration: 0.16)) {
                                notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
                            }
                    }
                    .map { rect in
                        rect.height - geometry.safeAreaInsets.bottom
                    }
                    .subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))
                    
                    NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillHideNotification)
                        .compactMap { notification in
                            CGFloat.zero
                    }
                    .subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))
                })
        }
    }
}

extension View {
    func adaptsToKeyboard() -> some View {
        return modifier(AdaptsToKeyboard())
    }
}


Usage:


var body: some View {
        NavigationView{
            ZStack {
                Color("Background").ignoresSafeArea()
                 VStack{
                      //Your content here
                 }
                 padding(.bottom)
             }
            .adaptsToKeyboard()
            .edgesIgnoringSafeArea(.bottom)
       }
}