18

I have a simple search list:


struct ContentView: View {
    @State var text:String = ""
    var items = 1...100
    var body: some View {
        VStack {
            List {
                TextField("Search", text: $text)
                Section{
                    ForEach(items.filter({"\($0)".contains(text)}),id: \.self){(i) in
                       Text("option \(i)")
                    }
                }
            }
        }
    }
}

iOS simulator screenshot

How can I make the keyboard close when scrolling for more than 2 cells/few points?

7 Answers 7

29

If you are using a ScrollView (probably also with a List but I haven't confirmed it), you could use the UIScrollView appearance, this will affect all ScrollViews though.

UIScrollView.appearance().keyboardDismissMode = .onDrag
Sign up to request clarification or add additional context in comments.

4 Comments

This is the best suggestion so far. Also works well with other views / components like a Slider on the same scroll view
This works for List, as I tested, and probably other scrollable views as well. I guess it is because List is a subclass of ScrollView
I found that if the Emoji keyboard is shown and you slide the emojis, this causes the keyboard to hide weirdly - any thoughts on how to fix that?
Use this only when you dont have any TextEditor or text views that scrolls because those also gets affected by the this universal configuration
15

As for now, since iOS 16 beta we have a new modifier scrollDismissesKeyboard() that allows to do exactly what you need.

In your example it should look like

struct ContentView: View {
    @State var text: String = ""
    var items = 1...100
    var body: some View {
        List {
            TextField("Search", text: $text)
            Section {
                ForEach(items.filter({"\($0)".contains(text)}), id: \.self) { (i) in
                    Text("option \(i)")
                }
            }
        }
        .scrollDismissesKeyboard(.interactively) // <<-- Put this line
    }
}

The scrollDismissesKeyboard() modifier has a parameter that determine the dismiss rules. Here are the possible values:

  • .automatic: Dismissing based on the context of the scroll.
  • .immediately: The keyboard will be dismissed as soon as any scroll happens.
  • .interactively: The keyboard will move/disappear inline with the user’s gesture.
  • .never: The keyboard will never dismissed when user is scrolling.

1 Comment

I ran into problems when using this in a view where the actual List/ScrollView did not cover entirety of the view above the keyboard. In that case, the scroll gesture would still dismiss the keyboard, but result in a jerky animation due to the resizing of the view (when the keyboard disappears). I was able to fix this by making the keyboard ignore the safe area (which places it on top of the view, rather than shifting the view up and displaying the keyboard below it). Here are the modifiers I used: .scrollDismissesKeyboard(.immediately).ignoresSafeArea(.keyboard, edges: .all)
12

A thorough discussion on how to resign the keyboard with various answers can be found for this question.

One solution to resign the keyboard on a drag gesture in the list is using a method on UIApplication window as shown below. For easier handling I created an extension on UIApplication and view modifier for this extension and finally an extension to View:

extension UIApplication {
    func endEditing(_ force: Bool) {
        self.windows
            .filter{$0.isKeyWindow}
            .first?
            .endEditing(force)
    }
}

struct ResignKeyboardOnDragGesture: ViewModifier {
    var gesture = DragGesture().onChanged{_ in
        UIApplication.shared.endEditing(true)
    }
    func body(content: Content) -> some View {
        content.gesture(gesture)
    }
}

extension View {
    func resignKeyboardOnDragGesture() -> some View {
        return modifier(ResignKeyboardOnDragGesture())
    }
}

So the final modifier for resigning the keyboard is just one modifier that has to be placed on the list like this:

List {
    ForEach(...) {
        //...
    }
}
.resignKeyboardOnDragGesture()

I have also implemented a pure swiftUI version of a search bar that might be interesting for you. You can find it in this answer.

3 Comments

I don't know if this is still actively monitored but for iOS 15, I got a warning that says 'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead. Would be helpful to add a code section for iOS 15.
One possibility to overcome the deprecation as of iOS 15 that I found here is to use let scenes = UIApplication.shared.connectedScenes let windowScene = scenes.first as? UIWindowScene let window = windowScene?.windows.first window?.endEditing(force)
But with my solution I had the issue, that pressing the back button on navigation views would require pressing it with a slight pan. It worked, but annoyed sometimes. Meanwhile I tested the above on-liner solution from vauxhaull and it seems to work well, but not having this issue.
7
Form {
    ...
}.gesture(DragGesture().onChanged { _ in
    UIApplication.shared.windows.forEach { $0.endEditing(false) }
})

3 Comments

I find that this interferes with the swipe to delete unfortunately.
Also with the Slider component
Strangely, it only works horizontally for me.
5

@FocusState wrapper along with .focused() TextField modifier can be useful.

struct ContentView: View {
    @FocusState private var focusedSearchField: Bool
    @State var text:String = ""
    var items = 1...100
    var body: some View {
        VStack {
            List {
                TextField("Search", text: $text)
                    .focused($focusedSearchField)
                Section{
                    ForEach(items.filter({"\($0)".contains(text)}),id: \.self){(i) in
                        Text("option \(i)")
                    }
                }
            } // to also allow swipes on items (theoretically)
            .simultaneousGesture(DragGesture().onChanged({ _ in
                focusedSearchField = false
            }))
            .onTapGesture { // dissmis on tap as well
                focusedSearchField = false
            }
        }
    }
}

Comments

2
struct EndEditingKeyboardOnDragGesture: ViewModifier {
    func body(content: Content) -> some View {
        content.highPriorityGesture (
            DragGesture().onChanged { _ in 
                UIApplication.shared.endEditing()
            }
        )
    }
}

extension View {
    func endEditingKeyboardOnDragGesture() -> some View {
        return modifier(EndEditingKeyboardOnDragGesture())
    }
}

1 Comment

Unfortunately, it interferes with the swipe gesture.
0

You can try to use this if the target iOS version is less than 16

.gesture(DragGesture().onChanged { _ in        
         UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),                                                                    to: nil, from: nil, for: nil)        
        })

eg:

struct ContentView: View {
    @State var text: String = ""
    var items = 1...100
    var body: some View {
        List {
            TextField("Search", text: $text)
            Section {
                ForEach(items.filter({"\($0)".contains(text)}), id: \.self) { (i) in
                    Text("option \(i)")
                }
            }
        }
        .gesture(DragGesture().onChanged { _ in        
         UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),                                                                    to: nil, from: nil, for: nil)        
        }) 
    }
}

struct ContentView: View {    @State var text: String = ""    var items = 1...100    var body: some View {        List {            TextField("Search", text: $text)            Section {                ForEach(items.filter({"\($0)".contains(text)}), id: \.self) { (i) in                    Text("option \(i)")                }            }        }            }}



Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.