17

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?

Daniel Meltzer
  • 349
  • 1
  • 3
  • 10

6 Answers6

26

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
vicegax
  • 4,709
  • 28
  • 37
  • This is the best suggestion so far. Also works well with other views / components like a `Slider` on the same scroll view – Juraj Blahunka Nov 15 '20 at 20:51
  • 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` – qsmy Jan 07 '21 at 01:24
  • 5
    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? – hyouuu Aug 10 '21 at 20:15
  • Use this only when you dont have any TextEditor or text views that scrolls because those also gets affected by the this universal configuration – Teffi Mar 31 '22 at 08:40
11

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.

user3687284
  • 2,206
  • 1
  • 15
  • 14
  • 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. – PipEvangelist Nov 03 '21 at 03:18
  • One possibility to overcome the deprecation as of iOS 15 that I found [here](https://developer.apple.com/forums/thread/682621) is to use let scenes = UIApplication.shared.connectedScenes let windowScene = scenes.first as? UIWindowScene let window = windowScene?.windows.first window?.endEditing(force) – user3687284 Jan 01 '22 at 15:45
  • 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](https://stackoverflow.com/questions/58540763/swiftui-close-keyboard-on-scroll/64536489#64536489) from [vauxhaull](https://stackoverflow.com/users/4691224/vauxhall) and it seems to work well, but not having this issue. – user3687284 Jan 01 '22 at 15:46
11

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.
siginur
  • 136
  • 1
  • 3
6
Form {
    ...
}.gesture(DragGesture().onChanged { _ in
    UIApplication.shared.windows.forEach { $0.endEditing(false) }
})
Steve Ham
  • 3,067
  • 1
  • 29
  • 36
4

@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
            }
        }
    }
}
Paul B
  • 3,989
  • 33
  • 46
1
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())
    }
}
Alex
  • 61
  • 7