How to ignore the horizontal safe area of the keyboard toolbar?

57 Views Asked by At

I use ToolbarItem(placement: .keyboard) to provide a button over the keyboard. However, it cannot achieve the full screen of the iPhone in landscape orientation. As shown in the screenshot below, it is really ugly...

enter image description here

Below is a sample code snippet, or you can try the DEMO Project:

struct ContentView: View {

  @State private var text: String = ""
  @FocusState private var isTextFieldFocused: Bool

  var body: some View {
    VStack {
      Text("A Text Field Below.")
      TextField("Enter text here.", text: $text)
        .frame(height: 44)
        .textFieldStyle(.roundedBorder)
        .focused($isTextFieldFocused)
    }
    .onAppear {
      self.isTextFieldFocused = true
    }
    .toolbar {
      ToolbarItem(placement: .keyboard) {
        _keyboardDismissButton()
      }
    }
  }

  @ViewBuilder
  private func _keyboardDismissButton() -> some View {
    Button {
      self.isTextFieldFocused = false
    } label: {
      Image(systemName: "keyboard.chevron.compact.down")
        .foregroundColor(.secondary)
        .frame(height: 44)
        .frame(maxWidth: .infinity)
        .contentShape(Rectangle())
    }
    .buttonStyle(.plain)
  }
}

I tried .ignoresSafeArea(.all, edges: .horizontal) or related combinations without any luck. Any modifiers on the button had no effect.

I know we can implement it in UIKit, just wondering if there is any workaround to handle it in SwiftUI. Or is this a known issue? I don't think this is the intended design...


Environment:
Xcode 15.0 (15A240d)
iOS 17.0

1

There are 1 best solutions below

3
On

I tried looking for some kind of native and standard solution but it seems there is some kind of limitation drawing things over that area except forcing it with offsets... This is based on my research and I hope someone proves me wrong, I'm still learning nonetheless.

So, what I did was the following:

  1. I've created a custom Rectangle component which takes in a boolean to indicate whether this is going to be placed left or right:

    @ViewBuilder
    private func KeyboardButtonRectangle(isLeading: Bool = true) -> some View {
        HStack {
            if !isLeading {
                Spacer(minLength: 0)
            }
            VStack {
                Rectangle()
                    /// Look for the right color here
                    .foregroundColor(.gray.opacity(0.05))
                    .contentShape(.rect)
                    .overlay(alignment: .top) {
                        Rectangle()
                            .frame(height: 1)
                            /// Look for the right color here
                            .foregroundColor(.gray.opacity(0.2))
                    }
            }
            .frame(width: 85, height: 45)
    
            if isLeading {
                Spacer(minLength: 0)
            }
    
        }
        .frame(maxWidth: .infinity)
        .offset(x: isLeading ? -100 : 100)
    }
    
  2. Then I used it like this in the already present toolbar:

    ToolbarItem(placement: .keyboard) {
         _keyboardDismissButton()
         .overlay(alignment: .leading) {
             KeyboardButtonRectangle()
         }
         .overlay(alignment: .trailing) {
              KeyboardButtonRectangle(isLeading: false)
         }
    
     }
    

One of the issues of going this way is that I did not know the exact color of the toolbar button there, and using .secondary created a dark grey Rectangle. If you know what the right color is I think it will be indistinguishable from the real button.

Here's the result I get:

Safe Area Keyboard Button

Let me know what you think about this!