skip to Main Content

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):

2

Answers


  1. 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
        }
    }
    
    Login or Signup to reply.
  2. The extra height comes from the container’s safe area, the area that contains the "home swipe bar" on iPhone 15 and other models.

    keyboardFrameEndUserInfoKey includes the container’s safe area too, but you only ignored the .keyboard safe area, and that’s why the red area appears higher than what you expect.

    One workaround is to ignore all safe areas depending on whether the keyboard is showing.

    // replace the existing ignoresSafeArea modifier with this:
    .ignoresSafeArea(keyboardHeight == 0 ? [] : .all)
    

    Alternatively, you can avoid using NotificationCenter, and do this in pure SwiftUI. All you need is to read the container safe area, and the total safe area, and subtract one from the other.

    GeometryReader { total in // this is the total safe area, because no safe area is ignored
        GeometryReader { container in // this is the container safe area, because we ignored the keyboard safe area
            VStack {
                TextField("Test", text: $text)
                Color.red
                    // we subtract it here:
                    .frame(height: total.safeAreaInsets.bottom - container.safeAreaInsets.bottom)
            }
            .frame(maxHeight: .infinity, alignment: .bottom)
        }
        .ignoresSafeArea(.keyboard)
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search