Как скрыть клавиатуру при использовании SwiftUI?


88

Как скрыть keyboardиспользование SwiftUIдля следующих случаев?

Случай 1

У меня есть, TextFieldи мне нужно скрыть, keyboardкогда пользователь нажимает returnкнопку.

Случай 2

У меня есть, TextFieldи мне нужно скрыть, keyboardкогда пользователь нажимает снаружи.

Как я могу это сделать SwiftUI?

Заметка:

Я не задавал вопросов по поводу UITextField. Я хочу сделать это с помощью SwifUI.TextField.


29
@DannyBuonocore Прочтите мой вопрос еще раз внимательно!
Hitesh Сурани

9
@DannyBuonocore Это не дубликат упомянутого вопроса. Этот вопрос касается SwiftUI, а другой - нормального UIKit
Johnykutty 07

1
@DannyBuonocore, пожалуйста, загляните на сайт developer.apple.com/documentation/swiftui, чтобы найти разницу между UIKit и SwiftUI. Спасибо
Hitesh Сурани

Я добавил здесь свое решение, надеюсь, оно вам поможет.
Виктор Кушнеров

Большинство решений здесь не работают должным образом, поскольку они отключают желаемые реакции на других управляющих нажатиях. Рабочее решение можно найти здесь: forum.developer.apple.com/thread/127196
Hardy,

Ответы:


79

Вы можете заставить первого респондента уйти в отставку, отправив действие общему приложению:

extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

Теперь вы можете использовать этот метод, чтобы закрыть клавиатуру, когда захотите:

struct ContentView : View {
    @State private var name: String = ""

    var body: some View {
        VStack {
            Text("Hello \(name)")
            TextField("Name...", text: self.$name) {
                // Called when the user tap the return button
                // see `onCommit` on TextField initializer.
                UIApplication.shared.endEditing()
            }
        }
    }
}

Если вы хотите закрыть клавиатуру одним касанием, вы можете создать полноэкранный белый вид с действием касания, которое вызовет endEditing(_:):

struct Background<Content: View>: View {
    private var content: Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content()
    }

    var body: some View {
        Color.white
        .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
        .overlay(content)
    }
}

struct ContentView : View {
    @State private var name: String = ""

    var body: some View {
        Background {
            VStack {
                Text("Hello \(self.name)")
                TextField("Name...", text: self.$name) {
                    self.endEditing()
                }
            }
        }.onTapGesture {
            self.endEditing()
        }
    }

    private func endEditing() {
        UIApplication.shared.endEditing()
    }
}

1
.keyWindowтеперь устарела. См . Ответ Лоренцо Сантини .
LinusGeffarth 08

3
Кроме того, .tapActionон был переименован в.onTapGesture
LinusGeffarth

Можно ли закрыть клавиатуру, когда станет активным альтернативный элемент управления? stackoverflow.com/questions/58643512/…
Ярм

1
Есть ли способ сделать это без белого фона, я использую распорки, и мне нужно, чтобы он обнаруживал жест касания на проставке. Также стратегия белого фона создает проблему на новых iPhone, где теперь есть дополнительное место на экране. Любая помощь приветствуется!
Джозеф Астрахан

Я опубликовал ответ, который улучшает ваш дизайн. Не стесняйтесь вносить правки в свой ответ, если хотите, мне наплевать на кредит.
Джозеф Астрахан

61

После множества попыток я нашел решение, которое (в настоящее время) не блокирует никакие элементы управления - добавление распознавателя жестов в UIWindow.

  1. Если вы хотите закрыть клавиатуру только на Tap снаружи (без обработки перетаскивания) - тогда достаточно использовать просто UITapGestureRecognizerи просто скопировать шаг 3:
  2. Создайте собственный класс распознавателя жестов, который работает с любыми прикосновениями:

    class AnyGestureRecognizer: UIGestureRecognizer {
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
            if let touchedView = touches.first?.view, touchedView is UIControl {
                state = .cancelled
    
            } else if let touchedView = touches.first?.view as? UITextView, touchedView.isEditable {
                state = .cancelled
    
            } else {
                state = .began
            }
        }
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
           state = .ended
        }
    
        override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
            state = .cancelled
        }
    }
    
  3. В SceneDelegate.swiftв func scene, добавьте следующий код:

    let tapGesture = AnyGestureRecognizer(target: window, action:#selector(UIView.endEditing))
    tapGesture.requiresExclusiveTouchType = false
    tapGesture.cancelsTouchesInView = false
    tapGesture.delegate = self //I don't use window as delegate to minimize possible side effects
    window?.addGestureRecognizer(tapGesture)  
    
  4. Реализуйте UIGestureRecognizerDelegateвозможность одновременного касания.

    extension SceneDelegate: UIGestureRecognizerDelegate {
        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
            return true
        }
    }
    

Теперь любая клавиатура в любом представлении будет закрыта при касании или перетаскивании наружу.

PS Если вы хотите закрыть только определенные текстовые поля - добавьте и удалите распознаватель жестов в окно при каждом вызове обратного вызова TextField onEditingChanged


3
Этот ответ должен быть вверху. Другие ответы не работают, когда в представлении есть другие элементы управления.
Imthath

1
@RolandLariotte обновил ответ, чтобы исправить это поведение, посмотрите новую реализацию AnyGestureRecognizer
Михаил

1
Отличный ответ. Работает безупречно. @Mikhail на самом деле интересно узнать, как удалить распознаватель жестов специально для некоторых текстовых полей (я создал автозаполнение с тегами, поэтому каждый раз, когда я нажимаю элемент в списке, я не хочу, чтобы это конкретное текстовое поле теряло фокус)
Pasta

1
это решение действительно отличное, но, использовав его около 3 месяцев, к сожалению, я обнаружил ошибку, непосредственно вызванную таким взломом. пожалуйста, имейте в
виду

1
Фантастический ответ! Интересно, как это будет реализовано в iOS 14 без сцены?
Dom

28

@ RyanTCB ответ хорош; вот несколько усовершенствований, которые упрощают использование и предотвращают потенциальный сбой:

struct DismissingKeyboard: ViewModifier {
    func body(content: Content) -> some View {
        content
            .onTapGesture {
                let keyWindow = UIApplication.shared.connectedScenes
                        .filter({$0.activationState == .foregroundActive})
                        .map({$0 as? UIWindowScene})
                        .compactMap({$0})
                        .first?.windows
                        .filter({$0.isKeyWindow}).first
                keyWindow?.endEditing(true)                    
        }
    }
}

«Исправление ошибки» - это просто то, что keyWindow!.endEditing(true)должно быть keyWindow?.endEditing(true)(да, вы можете утверждать, что этого не может быть).

Более интересно то, как вы можете его использовать. Например, предположим, что у вас есть форма с несколькими редактируемыми полями. Просто заверните это так:

Form {
    .
    .
    .
}
.modifier(DismissingKeyboard())

Теперь нажатие на любой элемент управления, который сам по себе не имеет клавиатуры, приведет к соответствующему закрытию.

(Проверено с бета-версией 7)


6
Хммм - нажатие на другие элементы управления больше не регистрируется. Событие проглочено.
Ярм

Я не могу повторить это - у меня все еще работает с использованием последних выпусков Apple от 11/1. Это сработало, а потом просто перестало работать у вас, или ??
Feldur

Если у вас есть DatePicker в форме, DatePicker больше не будет отображаться
Альберт

@Albert - это правда; чтобы использовать этот подход, вам придется разбить, где элементы украшены DismissingKeyboard (), до более тонкого уровня, который применяется к элементам, которые должны отклоняться, и избегает DatePicker.
Feldur

Использование этого кода приведет к воспроизведению предупрежденияCan't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
np2314

23

Я испытал это при использовании TextField внутри NavigationView. Это мое решение. Он закроет клавиатуру, когда вы начнете прокрутку.

NavigationView {
    Form {
        Section {
            TextField("Receipt amount", text: $receiptAmount)
            .keyboardType(.decimalPad)
           }
        }
     }
     .gesture(DragGesture().onChanged{_ in UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)})

Это приведет к странному поведению onDelete (проведите пальцем для удаления).
Тарек Халлак,

Это хорошо, но как насчет крана?
Danny182,

20

Я нашел другой способ отключить клавиатуру, который не требует доступа к keyWindowсвойству; на самом деле компилятор выдает предупреждение, используя

UIApplication.shared.keyWindow?.endEditing(true)

'keyWindow' устарело в iOS 13.0: не должно использоваться для приложений, поддерживающих несколько сцен, поскольку он возвращает ключевое окно для всех связанных сцен.

Вместо этого я использовал этот код:

UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)

15

SwiftUI в файле SceneDelegate.swift просто добавьте: .onTapGesture {window.endEditing (true)}

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(
                rootView: contentView.onTapGesture { window.endEditing(true)}
            )
            self.window = window
            window.makeKeyAndVisible()
        }
    }

этого достаточно для каждого просмотра с помощью клавиатуры в вашем приложении ...


4
Это дает еще одну проблему - у меня есть средство выбора в форме {} рядом с текстовым полем, оно перестало отвечать. Я не нашел решения, используя все ответы в этой теме. Но ваш ответ хорош для того, чтобы закрыть клавиатуру одним касанием в другом месте - если вы не используете средства выбора.
Налов

Здравствуйте. мой код `` var body: some View {NavigationView {Form {Section {TextField ("typesomething", text: $ c)} Section {Picker ("name", selection: $ sel) {ForEach (0 .. <200 ) {Text ("(self.array [$ 0])%")}}} `` «Клавиатура закрывается при нажатии в другом месте, но средство выбора перестало отвечать. Я не нашел способа заставить его работать.
Налов

2
Еще раз привет, на данный момент у меня есть два решения: первое - использовать родную клавиатуру, отклоненную на кнопке возврата, второе - немного изменить обработку нажатия (она же 'костыль') - window.rootViewController = UIHostingController (rootView : contentView.onTapGesture (count: 2, выполнить: {window.endEditing (true)})) Надеюсь, это поможет вам ...
Дим Ново

Здравствуйте. Спасибо. Второй способ решил это. Я использую цифровую клавиатуру, поэтому пользователи могут вводить только числа, у нее нет клавиши возврата. Я искал закрытие с нажатием.
Налов

это приведет к невозможности навигации по списку.
Цуй Минда

14

SwiftUI 2

Вот обновленное решение для SwiftUI 2 / iOS 14 (первоначально предложенное здесь Михаилом).

Он не использует AppDelegateни те, ни те, SceneDelegateкоторые отсутствуют, если вы используете жизненный цикл SwiftUI:

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear(perform: UIApplication.shared.addTapGestureRecognizer)
        }
    }
}

extension UIApplication {
    func addTapGestureRecognizer() {
        guard let window = windows.first else { return }
        let tapGesture = UITapGestureRecognizer(target: window, action: #selector(UIView.endEditing))
        tapGesture.requiresExclusiveTouchType = false
        tapGesture.cancelsTouchesInView = false
        tapGesture.delegate = self
        window.addGestureRecognizer(tapGesture)
    }
}

extension UIApplication: UIGestureRecognizerDelegate {
    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true // set to `false` if you don't want to detect tap during other gestures
    }
}

Вот пример того, как распознавать одновременные жесты, кроме жестов длительного нажатия:

extension UIApplication: UIGestureRecognizerDelegate {
    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return !otherGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self)
    }
}

2
Это прекрасно работает! Спасибо за решение
NotAPhoenix

2
Это должно быть наверху, потому что учитывается новый жизненный цикл SwiftUI.
Карлособедгомес

Это прекрасно работает. Однако, если я дважды коснусь текстового поля, вместо выделения текста клавиатура исчезнет. Есть идеи, как я могу разрешить двойное нажатие для выбора?
Гэри

@Gary В нижнем расширении вы можете увидеть строку с комментарием, для которого установлено значение false, если вы не хотите обнаруживать касание во время других жестов . Просто установите его return false.
pawello2222

Установка значения false работает, но клавиатура также не отключается, если кто-то долго нажимает, перетаскивает или прокручивает за пределами текстовой области. Есть ли способ установить значение false только для двойных щелчков (желательно двойных щелчков внутри текстового поля, но подойдут даже все двойные щелчки).
Гэри

11

Мое решение, как скрыть программную клавиатуру, когда пользователи нажимают снаружи. Вам нужно использовать contentShapewith, onLongPressGestureчтобы обнаружить весь контейнер View. onTapGestureтребуется, чтобы не блокировать фокус на TextField. Вы можете использовать onTapGestureвместо, onLongPressGestureно элементы NavigationBar работать не будут.

extension View {
    func endEditing() {
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

struct KeyboardAvoiderDemo: View {
    @State var text = ""
    var body: some View {
        VStack {
            TextField("Demo", text: self.$text)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .contentShape(Rectangle())
        .onTapGesture {}
        .onLongPressGesture(
            pressing: { isPressed in if isPressed { self.endEditing() } },
            perform: {})
    }
}

Это отлично сработало, я использовал его немного по-другому и должен был убедиться, что он вызывается в основном потоке.
keegan3d

7

добавьте этот модификатор в представление, которое вы хотите обнаруживать касания пользователя

.onTapGesture {
            let keyWindow = UIApplication.shared.connectedScenes
                               .filter({$0.activationState == .foregroundActive})
                               .map({$0 as? UIWindowScene})
                               .compactMap({$0})
                               .first?.windows
                               .filter({$0.isKeyWindow}).first
            keyWindow!.endEditing(true)

        }

7

Я предпочитаю использовать .onLongPressGesture(minimumDuration: 0), который не заставляет клавиатуру мигать, когда TextViewактивируется другая (побочный эффект .onTapGesture). Код скрытой клавиатуры может быть функцией многократного использования.

.onTapGesture(count: 2){} // UI is unresponsive without this line. Why?
.onLongPressGesture(minimumDuration: 0, maximumDistance: 0, pressing: nil, perform: hide_keyboard)

func hide_keyboard()
{
    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}

Еще мерцание при использовании этого метода.
Дэниел Райан

Это отлично сработало, я использовал его немного по-другому и должен был убедиться, что он вызывается в основном потоке.
keegan3d

6

Потому keyWindowчто устарело.

extension View {
    func endEditing(_ force: Bool) {
        UIApplication.shared.windows.forEach { $0.endEditing(force)}
    }
}

1
forceПараметр не используется. Должно быть{ $0.endEditing(force)}
Давиде

5

Похоже, endEditingэто единственное решение, на которое указал @rraphael.
Самый чистый пример, который я видел до сих пор:

extension View {
    func endEditing(_ force: Bool) {
        UIApplication.shared.keyWindow?.endEditing(force)
    }
}

а затем использовать его в onCommit:


2
.keyWindowтеперь устарела. См . Ответ Лоренцо Сантини .
LinusGeffarth 08

Он обесценивается в iOS 13+
Ахмадреза,

4

Расширяя ответ @Feldur (который был основан на @RyanTCB), вот еще более выразительное и мощное решение, позволяющее отключать клавиатуру с помощью других жестов, чем onTapGestureвы можете указать, что вы хотите, в вызове функции.

Применение

// MARK: - View
extension RestoreAccountInputMnemonicScreen: View {
    var body: some View {
        List(viewModel.inputWords) { inputMnemonicWord in
            InputMnemonicCell(mnemonicInput: inputMnemonicWord)
        }
        .dismissKeyboard(on: [.tap, .drag])
    }
}

Или используя All.gestures(просто сахар для Gestures.allCases🍬)

.dismissKeyboard(on: All.gestures)

Код

enum All {
    static let gestures = all(of: Gestures.self)

    private static func all<CI>(of _: CI.Type) -> CI.AllCases where CI: CaseIterable {
        return CI.allCases
    }
}

enum Gestures: Hashable, CaseIterable {
    case tap, longPress, drag, magnification, rotation
}

protocol ValueGesture: Gesture where Value: Equatable {
    func onChanged(_ action: @escaping (Value) -> Void) -> _ChangedGesture<Self>
}
extension LongPressGesture: ValueGesture {}
extension DragGesture: ValueGesture {}
extension MagnificationGesture: ValueGesture {}
extension RotationGesture: ValueGesture {}

extension Gestures {
    @discardableResult
    func apply<V>(to view: V, perform voidAction: @escaping () -> Void) -> AnyView where V: View {

        func highPrio<G>(
             gesture: G
        ) -> AnyView where G: ValueGesture {
            view.highPriorityGesture(
                gesture.onChanged { value in
                    _ = value
                    voidAction()
                }
            ).eraseToAny()
        }

        switch self {
        case .tap:
            // not `highPriorityGesture` since tapping is a common gesture, e.g. wanna allow users
            // to easily tap on a TextField in another cell in the case of a list of TextFields / Form
            return view.gesture(TapGesture().onEnded(voidAction)).eraseToAny()
        case .longPress: return highPrio(gesture: LongPressGesture())
        case .drag: return highPrio(gesture: DragGesture())
        case .magnification: return highPrio(gesture: MagnificationGesture())
        case .rotation: return highPrio(gesture: RotationGesture())
        }

    }
}

struct DismissingKeyboard: ViewModifier {

    var gestures: [Gestures] = Gestures.allCases

    dynamic func body(content: Content) -> some View {
        let action = {
            let forcing = true
            let keyWindow = UIApplication.shared.connectedScenes
                .filter({$0.activationState == .foregroundActive})
                .map({$0 as? UIWindowScene})
                .compactMap({$0})
                .first?.windows
                .filter({$0.isKeyWindow}).first
            keyWindow?.endEditing(forcing)
        }

        return gestures.reduce(content.eraseToAny()) { $1.apply(to: $0, perform: action) }
    }
}

extension View {
    dynamic func dismissKeyboard(on gestures: [Gestures] = Gestures.allCases) -> some View {
        return ModifiedContent(content: self, modifier: DismissingKeyboard(gestures: gestures))
    }
}

Слово предостережения

Обратите внимание, что если вы используете все жесты, они могут конфликтовать, и я не придумал какого-либо изящного решения для этого.


что означает eraseToAny()
Ravindra_Bhati

2

Этот способ позволяет скрыть клавиатуру на проставках!

Сначала добавьте эту функцию (Кредит предоставлен: Каспер Зандберген, из SwiftUI не может нажать в Spacer of HStack )

extension Spacer {
    public func onTapGesture(count: Int = 1, perform action: @escaping () -> Void) -> some View {
        ZStack {
            Color.black.opacity(0.001).onTapGesture(count: count, perform: action)
            self
        }
    }
}

Затем добавьте следующие 2 функции (Credit Given To: rraphael, из этого вопроса)

extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

Приведенная ниже функция будет добавлена ​​в ваш класс View, просто обратитесь к верхнему ответу здесь от rraphael для получения более подробной информации.

private func endEditing() {
   UIApplication.shared.endEditing()
}

Наконец, теперь вы можете просто позвонить ...

Spacer().onTapGesture {
    self.endEditing()
}

Теперь любая разделительная область закроет клавиатуру. Больше нет необходимости в большом белом фоне!

Вы можете гипотетически применить эту технику extensionк любым элементам управления, которые вам нужны для поддержки TapGestures, которые в настоящее время этого не делают, и вызывать onTapGestureфункцию в сочетании с self.endEditing()закрытием клавиатуры в любой ситуации, в которой вы хотите.


Теперь у меня вопрос: как запустить фиксацию в текстовом поле, когда клавиатура уйдет таким образом? в настоящее время «фиксация» срабатывает только при нажатии клавиши возврата на клавиатуре iOS.
Джозеф Астрахан

2

Пожалуйста, проверьте https://github.com/michaelhenry/KeyboardAvoider

Просто включите его KeyboardAvoider {}поверх основного экрана и все.

KeyboardAvoider {
    VStack { 
        TextField()
        TextField()
        TextField()
        TextField()
    }

}

Это не работает для представления формы с текстовыми полями. Форма не отображается.
Нильс

2

Основываясь на ответе @ Sajjon, вот решение, позволяющее отключать клавиатуру при нажатии, длительном нажатии, перетаскивании, увеличении и повороте жестов по вашему выбору.

Это решение работает в XCode 11.4

Использование для получения поведения, заданного @IMHiteshSurani

struct MyView: View {
    @State var myText = ""

    var body: some View {
        VStack {
            DismissingKeyboardSpacer()

            HStack {
                TextField("My Text", text: $myText)

                Button("Return", action: {})
                    .dismissKeyboard(on: [.longPress])
            }

            DismissingKeyboardSpacer()
        }
    }
}

struct DismissingKeyboardSpacer: View {
    var body: some View {
        ZStack {
            Color.black.opacity(0.0001)

            Spacer()
        }
        .dismissKeyboard(on: Gestures.allCases)
    }
}

Код

enum All {
    static let gestures = all(of: Gestures.self)

    private static func all<CI>(of _: CI.Type) -> CI.AllCases where CI: CaseIterable {
        return CI.allCases
    }
}

enum Gestures: Hashable, CaseIterable {
    case tap, longPress, drag, magnification, rotation
}

protocol ValueGesture: Gesture where Value: Equatable {
    func onChanged(_ action: @escaping (Value) -> Void) -> _ChangedGesture<Self>
}

extension LongPressGesture: ValueGesture {}
extension DragGesture: ValueGesture {}
extension MagnificationGesture: ValueGesture {}
extension RotationGesture: ValueGesture {}

extension Gestures {
    @discardableResult
    func apply<V>(to view: V, perform voidAction: @escaping () -> Void) -> AnyView where V: View {

        func highPrio<G>(gesture: G) -> AnyView where G: ValueGesture {
            AnyView(view.highPriorityGesture(
                gesture.onChanged { _ in
                    voidAction()
                }
            ))
        }

        switch self {
        case .tap:
            return AnyView(view.gesture(TapGesture().onEnded(voidAction)))
        case .longPress:
            return highPrio(gesture: LongPressGesture())
        case .drag:
            return highPrio(gesture: DragGesture())
        case .magnification:
            return highPrio(gesture: MagnificationGesture())
        case .rotation:
            return highPrio(gesture: RotationGesture())
        }
    }
}

struct DismissingKeyboard: ViewModifier {
    var gestures: [Gestures] = Gestures.allCases

    dynamic func body(content: Content) -> some View {
        let action = {
            let forcing = true
            let keyWindow = UIApplication.shared.connectedScenes
                .filter({$0.activationState == .foregroundActive})
                .map({$0 as? UIWindowScene})
                .compactMap({$0})
                .first?.windows
                .filter({$0.isKeyWindow}).first
            keyWindow?.endEditing(forcing)
        }

        return gestures.reduce(AnyView(content)) { $1.apply(to: $0, perform: action) }
    }
}

extension View {
    dynamic func dismissKeyboard(on gestures: [Gestures] = Gestures.allCases) -> some View {
        return ModifiedContent(content: self, modifier: DismissingKeyboard(gestures: gestures))
    }
}

2

Вы можете полностью избежать взаимодействия с UIKit и реализовать его на чистом SwiftUI . Просто добавьте .id(<your id>)модификатор к вашему TextFieldи измените его значение всякий раз, когда вы хотите отключить клавиатуру (при смахивании, просмотре, нажатии кнопки и т. Д.).

Пример реализации:

struct MyView: View {
    @State private var text: String = ""
    @State private var textFieldId: String = UUID().uuidString

    var body: some View {
        VStack {
            TextField("Type here", text: $text)
                .id(textFieldId)

            Spacer()

            Button("Dismiss", action: { textFieldId = UUID().uuidString })
        }
    }
}

Обратите внимание, что я тестировал его только в последней бета-версии Xcode 12, но он должен работать со старыми версиями (даже Xcode 11) без каких-либо проблем.


0

ReturnКлавиша клавиатуры

В дополнение ко всем ответам о касании вне текстового поля, вы можете захотеть закрыть клавиатуру, когда пользователь нажимает клавишу возврата на клавиатуре:

определите эту глобальную функцию:

func resignFirstResponder() {
    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}

И добавьте в onCommitаргумент его:

TextField("title", text: $text, onCommit:  {
    resignFirstResponder()
})

Льготы

  • Вы можете позвонить из любого места
  • Он не зависит от UIKit или SwiftUI (может использоваться в приложениях для Mac)
  • Работает даже в iOS 13

Демо

демо


0

До сих пор вышеуказанные параметры не работали для меня, потому что у меня есть форма и внутренние кнопки, ссылки, средство выбора ...

Я создаю работающий ниже код с помощью приведенных выше примеров.

import Combine
import SwiftUI

private class KeyboardListener: ObservableObject {
    @Published var keyabordIsShowing: Bool = false
    var cancellable = Set<AnyCancellable>()

    init() {
        NotificationCenter.default
            .publisher(for: UIResponder.keyboardWillShowNotification)
            .sink { [weak self ] _ in
                self?.keyabordIsShowing = true
            }
            .store(in: &cancellable)

       NotificationCenter.default
            .publisher(for: UIResponder.keyboardWillHideNotification)
            .sink { [weak self ] _ in
                self?.keyabordIsShowing = false
            }
            .store(in: &cancellable)
    }
}

private struct DismissingKeyboard: ViewModifier {
    @ObservedObject var keyboardListener = KeyboardListener()

    fileprivate func body(content: Content) -> some View {
        ZStack {
            content
            Rectangle()
                .background(Color.clear)
                .opacity(keyboardListener.keyabordIsShowing ? 0.01 : 0)
                .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
                .onTapGesture {
                    let keyWindow = UIApplication.shared.connectedScenes
                        .filter({ $0.activationState == .foregroundActive })
                        .map({ $0 as? UIWindowScene })
                        .compactMap({ $0 })
                        .first?.windows
                        .filter({ $0.isKeyWindow }).first
                    keyWindow?.endEditing(true)
                }
        }
    }
}

extension View {
    func dismissingKeyboard() -> some View {
        ModifiedContent(content: self, modifier: DismissingKeyboard())
    }
}

Применение:

 var body: some View {
        NavigationView {
            Form {
                picker
                button
                textfield
                text
            }
            .dismissingKeyboard()

-2

SwiftUI, выпущенный в июне 2020 года с Xcode 12 и iOS 14, добавляет модификатор hideKeyboardOnTap (). Это должно решить ваш случай номер 2. Решение для вашего случая номер 1 предоставляется бесплатно с Xcode 12 и iOS 14: клавиатура по умолчанию для TextField автоматически скрывается при нажатии кнопки Return.


1
В iOS14 нет модификатора hideKeyboardOnTap
Тео Сартори

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.