Search code examples
iosswiftswiftuiios17

Does `UIResponder.keyboardWillShowNotification` provde incorrect height on iOS devices with curved screens?


I'm using UIResponder.keyboardWillShowNotification and the user info it provides to perform manual keyboard avoidance. Interestingly my method works fine on devices with square screen corners (iPhone SE 3rd Generation) but not on devices with curved corner screens (e.g. iPhone 15 Pro Max). When there's curved corners the height seems to be a bit too large.

I've also tried accounting for the height of the container's bottom safe area but that height didn't exactly match up with the extra height I'm seeing.

import SwiftUI

struct ContentView: View {
    @State private var text = ""
    
    @State private var keyboardHeight: CGFloat = .zero
    
    var body: some View {
        VStack {
            TextField("Test", text: $text)
            Color.red
                .frame(height: keyboardHeight)
        }
        .animation(.default.delay(0), value: keyboardHeight)
        .frame(maxHeight: .infinity, alignment: .bottom)
        .ignoresSafeArea(.keyboard)
        .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) {
            keyboardHeight = ($0.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect).height
        }
        .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
            keyboardHeight = .zero
        }
    }
}

Device with rounded corners (bad):

Device with square corners (good):


Solution

  • It's because of safe area inset at the bottom. Try this:

    GeometryReader { geo in // <- wrap inside GeometryReader
        VStack {
            TextField("Test", text: $text)
                .autocorrectionDisabled(true)
            Color.red
                .frame(height: keyboardHeight)
        }
        .animation(.default.delay(0), value: keyboardHeight)
        .frame(maxHeight: .infinity, alignment: .bottom)
        .ignoresSafeArea(.keyboard)
        .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) {
            let height = ($0.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect).height
            keyboardHeight = height - geo.safeAreaInsets.bottom
        }
        .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
            keyboardHeight = .zero
        }
    }
    

    enter image description here