Search code examples
iosswiftuiuihostingcontrollersafearea

Disable keyboard avoidance in a bottom sheet


TLDR: The view modifier .ignoresSafeArea(.keyboard) does not appear to work when used inside a bottom sheet. Is there a workaround?

In a SwiftUI View, tapping a TextField invokes the keyboard and the Textfield then moves upwards to avoid the keyboard.

struct ContentView: View {
    
    @State var mytext: String = "Some text"   
    var body: some View {      
        VStack {
           Spacer()
           TextField("abc", text: $mytext)
           Spacer()
        }            
    }
}

This keyboard avoidance behaviour can be disabled by adding the .ignoresSafeArea modifier

struct ContentView: View {
    
    @State var mytext: String = "Some text"   
    var body: some View {      
        VStack {
           Spacer()
           TextField("abc", text: $mytext)
           Spacer()
        }
        .ignoresSafeArea(.keyboard, edges: .bottom)   
    }
}

and the TextField no longer moves upwards.

If this technique is applied inside to a view in a bottom sheet it no longer works and the entire sheet is pushed up by the keyboard.

struct ContentView: View {
    
    @State var mytext: String = "Some text"
    @State var isPresented: Bool = true
    
    var body: some View {
        Color.mint
            .sheet(isPresented: $isPresented) {
            
                VStack {
                    Spacer()
                    TextField("abc", text: $mytext)
                    Spacer()
            }
            .presentationDetents( [.fraction(0.33)] )
            .ignoresSafeArea(.keyboard, edges: .bottom)
        }
    }
}

I've tried applying .ignoresSafeArea(.keyboard, edges: .bottom) to every view thats exposed in the code with no success.

I suspect that the bug is due to the bottom sheet implementation using a UIHostingController internally. This can been seen using Xcode's Debug View Hierarchy tool.

Others have described how UIHostingController does not respect the .ignoresSafeArea(.keyboard, edges: .bottom) modifier and have developed workarounds but these are not applicable here because the UIHostingController is created internally, not explicitly in my code.

Is there any way to get the view inside the sheet to ignore the keyboard and stay put?

I'm open to any and all suggestions. Thanks!


Solution

  • Others have described how UIHostingController does not respect the .ignoresSafeArea(.keyboard, edges: .bottom) modifier and have developed workarounds but these are not applicable here because the UIHostingController is created internally, not explicitly in my code.

    The workaround from Peter Steinberger’s blog post you’re referring to can actually be used in the context of pure SwiftUI views as well.

    You’d need to find the enclosing hosting view first, which can be achieved either by installing a UIViewRepresentable and traversing the underlying view hierarchy or by using a library that helps with this task. Once found, you’d swizzle its keyboardWillShowWithNotification: and keyboardWillHideWithNotification: methods to turn them into noops.

    Here is a gist of how this could look like. The snippet uses SwiftUI Introspect for finding the hosting view (here applied only on iOS 17), InterposeKit for swizzling, and finally, a custom associated objects API for storing off a flag indicating whether the patch has already been applied to a particular hosting view.

    To make use of the keyboardAvoidanceDisabled() modifier, you’d simply attach it to the view that gets affected by keyboard avoidance.

    Please keep in mind that this patch uses private API!