I have a very basic view that has a background (Color.red) and some content inside a ScrollView. The view worked great until I noticed something. After selecting the text field and tappint at the background, the keyboard wasn't dismissing. Even if I clicked somewhere that had nothing visually (expect the background) it still didn't work.
struct ContentView: View {
@State private var filter = ""
var body: some View {
ZStack {
Color.red.ignoresSafeArea(.all)
.onTapGesture {
// Dismiss keyboard
}
ScrollView {
VStack(spacing: 8) {
TextField("Some Filter", text: $filter)
.textFieldStyle(.roundedBorder)
Button("clicky click") {
// Fetch data
}
.buttonStyle(.borderedProminent)
// Other data result related stuff
}
}
.refreshable {
// Fetch data
}
}
}
}
So to solve it I tried doing these below:
I tried to add .allowsHitTesting(false) but it completely bricked the scroll view.
(Expected)
I tried to add a invisible view to the scroll view that did the same thing. But it became tiny. I tried to force it to be big but the scroll view became scrollable down to nothingness.
I tried to add it as a dynamic footer (the dynamicly streching ones). This worked, but it was way too costly for what it was doing. And it way too overcomplicated my view.
I'm running iOS 15. And I'm looking for a solution that isn't too complicated. Thanks in advance.
The tap gesture probably needs to be attached to a view inside or above the ScrollView. So you could try wrapping the ScrollView with a GeometryReader. Then:
VStack to the size delivered by the GeometryReader.VStack.The only issue I found with this was that the safe area insets were not filled with the background color, even when .ignoresSafeArea() was applied. So as a fallback, the same color can be applied as background to the ScrollView too. This means, the safe areas are not receptive to taps, but perhaps this is acceptable.
struct ContentView: View {
@State private var filter = ""
@FocusState private var isFocused: Bool
var body: some View {
GeometryReader { proxy in
ScrollView {
VStack(spacing: 8) {
TextField("Some Filter", text: $filter)
.textFieldStyle(.roundedBorder)
.focused($isFocused)
Button("clicky click") {
// Fetch data
}
.buttonStyle(.borderedProminent)
// Other data result related stuff
}
.frame(minWidth: proxy.size.width, minHeight: proxy.size.height, alignment: .topLeading)
.background {
Color.red
.ignoresSafeArea()
.onTapGesture {
isFocused = false
}
}
}
.background(.red)
.refreshable {
// Fetch data
}
}
}
}

Instead of applying the same background to both the VStack and the ScrollView, the background to the VStack could just use Color.clear instead. This would also be a suitable approach for when the background behind the ScrollView is something more elaborate than a simple color, such as when an image or gradient is used, or when no background is required at all.
Doing it this way, it is necessary to apply a .contentShape to the clear background, to make it receptive to taps:
VStack(spacing: 8) {
// ...
}
.frame(minWidth: proxy.size.width, minHeight: proxy.size.height, alignment: .topLeading)
.background {
Color.clear
.contentShape(.rect)
.onTapGesture {
isFocused = false
}
}
I went ahead and imagined that you may eventually need more than just one TextField.
I put the UIKit dismiss function onTap { of ScrollView }, and that worked really well. I could still use buttons, and select new TextFields but there was a keyboard flicker when I switched from one field to the another. It would dismiss and reappear. Oddly enough, .onTapGuesture { // any code or nil } kept the keyboard alive from the transition from one field to another.
@available ( iOS 15.0 , * )
struct ContentView: View {
@State var textArray: Array < String > = Array ( repeating: "" , count: 5 )
var body: some View {
ScrollView {
VStack ( alignment: .leading , spacing: 20 ) {
ForEach ( self.textArray.indices , id: \.self ) {
TextField ( "TextField #\( $0 )" , text: self.$textArray [ $0 ] )
.onTapGesture { }
Button ( "Clicky Click" ) { hideKeyboard() }
}
}
}
.onTapGesture { self.hideKeyboard() }
.textFieldStyle ( .roundedBorder )
.buttonStyle ( .borderedProminent )
}
private func hideKeyboard() {
UIApplication.shared.sendAction ( #selector ( UIResponder.resignFirstResponder ) , to: nil , from: nil , for: nil )
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With