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

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

Дело 1

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

Дело 2

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

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

Примечание:

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

37 ответов

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

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

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

Если вы хотите закрыть клавиатуру касанием, вы можете создать полноэкранный белый вид с действием касания, которое вызовет 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(self.$name, placeholder: Text("Name...")) {
                    self.endEditing(true)
                }
            }
        }.tapAction {
            self.endEditing(true)
        }
    }

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

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)
    }
}

После множества попыток я нашел решение, которое (в настоящее время) не блокирует никакие элементы управления - добавление распознавателя жестов в 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

Я испытал это при использовании 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)})

@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)

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

UIApplication.shared.keyWindow?.endEditing(true)

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

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

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

Вы можете полностью избежать взаимодействия с 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) без каких-либо проблем.

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()
        }
    }

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

Начиная с iOS 15 вы можете использовать @FocusState

      struct ContentView: View {
    
    @Binding var text: String
    
    private enum Field: Int {
        case yourTextEdit
    }

    @FocusState private var focusedField: Field?

    var body: some View {
        VStack {
            TextEditor(text: $speech.text.bound)
                .padding(Edge.Set.horizontal, 18)
                .focused($focusedField, equals: .yourTextEdit)
        }.onTapGesture {
            if (focusedField != nil) {
                focusedField = nil
            }
        }
    }
}

Мое решение как скрыть программную клавиатуру, когда пользователи нажимают снаружи. Вам нужно использоватьcontentShape с 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: {})
    }
}

В iOS15 это работает безупречно.

      VStack {
    // Some content
}
.onTapGesture {
    // Hide Keyboard
    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
.gesture(
    DragGesture(minimumDistance: 0, coordinateSpace: .local).onEnded({ gesture in
        // Hide keyboard on swipe down
        if gesture.translation.height > 0 {
            UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
        }
}))

В вашем TextField больше ничего не требуется, и оба смахивания вниз вместе с нажатием будут работать, чтобы скрыть его. То, как я использую это, это то, что на моем мастере Я добавляю этот код, и тогда все, что ниже, будет работать. Единственным исключением будет то, что любой нужно было бы добавить это к нему, поскольку это действует на другое состояние.

Я предпочитаю использовать .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)
}

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

.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 не рекомендуется.

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

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

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

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

Расширяя ответ с помощью josefdolezal выше , вы можете скрыть клавиатуру, когда пользователь нажимает где-нибудь за пределами текстового поля, как показано ниже:

      struct SwiftUIView: View {
        @State private var textFieldId: String = UUID().uuidString // To hidekeyboard when tapped outside textFields
        @State var fieldValue = ""
        var body: some View {
            VStack {
                TextField("placeholder", text: $fieldValue)
                    .id(textFieldId)
                    .onTapGesture {} // So that outer tap gesture has no effect on field

                // any more views

            }
            .onTapGesture { // whenever tapped within VStack
                textFieldId = UUID().uuidString 
               //^ this will remake the textfields hence loosing keyboard focus!
            }
        }
    }

Клавиатуры 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

Демо

демо

Расширяя ответ @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))
    }
}

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

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

Я обнаружил, что очень хорошо работает:

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

Затем добавьте в структуру представления:

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

потом

      struct YourView: View {
    var body: some View {
       ParentView {
           //...
       }.contentShape(Rectangle()) //<---- This is key!
        .onTapGesture {endEditing()} 
     }
 }
    

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

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

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

}

Что ж, самое простое решение для меня - просто использовать библиотеку https://github.com/hackiftekhar/IQKeyboardManager

Поддержка SwiftUI несколько ограничена, я использую ее, помещая этот код в структуру @main:

      import IQKeyboardManagerSwift

@main
struct MyApp: App {
            
    init(){
        IQKeyboardManager.shared.enable = true
        IQKeyboardManager.shared.shouldResignOnTouchOutside = true
        
    }

    ...
}

Сначала добавьте расширение в свой код

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

Используйте его как модификатор, чтобы закрыть клавиатуру при нажатииDoneкнопка.

         .toolbar{
    ToolbarItemGroup(placement: .keyboard){
         Spacer()
         Button("Done"){
            UIApplication.shared.dismissKeyboard()
          }
      }
  }

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

Сначала добавьте эту функцию (Кредит предоставлен: Casper Zandbergen, из 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() чтобы закрыть клавиатуру в любой ситуации.

Основываясь на ответе @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))
    }
}

Простое решение для нажатия кнопки «снаружи», которое сработало для меня:

Сначала укажите ZStack перед всеми представлениями. В нем поместите фон (с выбранным вами цветом) и укажите Жест касания. В вызове жеста вызовите sendAction, который мы видели выше:

      import SwiftUI

struct MyView: View {
    private var myBackgroundColor = Color.red
    @State var text = "text..."

var body: some View {
    ZStack {
        self.myBackgroundColor.edgesIgnoringSafeArea(.all)
            .onTapGesture(count: 1) {
                UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
        }
        TextField("", text: $text)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .padding()
    }
  }
}
extension UIApplication {
   func endEditing() {
       sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
  }
}

с помощью и на + iOS 15

используя и@FocusStateвы можете отклонить клавиатуру при любом нажатии возврата, или вы можете выбрать другое, чтобы затем получить фокус:

      struct ContentView: View {
  private enum Field: Int, CaseIterable {
    case username, password
  }
  
  @State private var username: String = ""
  @State private var password: String = ""
  
  @FocusState private var focusedField: Field?
  
  var body: some View {
    NavigationView {
      Form {
        TextField("Username", text: $username)
          .focused($focusedField, equals: .username)
        SecureField("Password", text: $password)
          .focused($focusedField, equals: .password)
      }
      .onSubmit {
        fieldInFocus = nil
      }
    }
  }
}

Или, если вы хотите использовать.onSubmitчтобы сосредоточить внимание на другомTextField:

        .onSubmit {
      } if fieldInFocus == .email {
        fieldInFocus = .password
      } else if fieldInFocus == .password {
        fieldInFocus = nil
      }
  }

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

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

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()

Более чистый собственный SwiftUI способ отключить клавиатуру с помощью касания без блокировки каких-либо сложных форм или чего-то еще ... кредит @user3441734 за то, что он пометил GestureMask как чистый подход.

  1. Монитор UIWindow.keyboardWillShowNotification / willHide

  2. Передайте текущее состояние клавиатуры через EnvironmentKey, установленный в корневом представлении /

Протестировано на iOS 14.5.

Прикрепите жест увольнения к форме

      Form { }
    .dismissKeyboardOnTap()

Настроить монитор в корневом представлении

      // Root view
    .environment(\.keyboardIsShown, keyboardIsShown)
    .onDisappear { dismantleKeyboarMonitors() }
    .onAppear { setupKeyboardMonitors() }

// Monitors

    @State private var keyboardIsShown = false
    @State private var keyboardHideMonitor: AnyCancellable? = nil
    @State private var keyboardShownMonitor: AnyCancellable? = nil
    
    func setupKeyboardMonitors() {
        keyboardShownMonitor = NotificationCenter.default
            .publisher(for: UIWindow.keyboardWillShowNotification)
            .sink { _ in if !keyboardIsShown { keyboardIsShown = true } }
        
        keyboardHideMonitor = NotificationCenter.default
            .publisher(for: UIWindow.keyboardWillHideNotification)
            .sink { _ in if keyboardIsShown { keyboardIsShown = false } }
    }
    
    func dismantleKeyboarMonitors() {
        keyboardHideMonitor?.cancel()
        keyboardShownMonitor?.cancel()
    }

SwiftUI Gesture + Sugar

      
struct HideKeyboardGestureModifier: ViewModifier {
    @Environment(\.keyboardIsShown) var keyboardIsShown
    
    func body(content: Content) -> some View {
        content
            .gesture(TapGesture().onEnded {
                UIApplication.shared.resignCurrentResponder()
            }, including: keyboardIsShown ? .all : .none)
    }
}

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

extension View {

    /// Assigns a tap gesture that dismisses the first responder only when the keyboard is visible to the KeyboardIsShown EnvironmentKey
    func dismissKeyboardOnTap() -> some View {
        modifier(HideKeyboardGestureModifier())
    }
    
    /// Shortcut to close in a function call
    func resignCurrentResponder() {
        UIApplication.shared.resignCurrentResponder()
    }
}

EnvironmentKey

      extension EnvironmentValues {
    var keyboardIsShown: Bool {
        get { return self[KeyboardIsShownEVK] }
        set { self[KeyboardIsShownEVK] = newValue }
    }
}

private struct KeyboardIsShownEVK: EnvironmentKey {
    static let defaultValue: Bool = false
}

расширение UIView{

переопределить open func touchesBegan(_ touches: Set, with event: UIEvent?) {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)

}}

iOS 13+

Один простой хак для iOS13+ — установить переменную состояния для «отключено» для каждого отдельного текстового поля. Очевидно, что это не идеально, но в некоторых случаях может помочь.

Как только вы установитеdisabled = True, то все связанные респонденты автоматически уходят в отставку.

      @State var isEditing: Bool
@State var text: String

....

      TextField("Text", text: self.$text).disabled(!self.isEditing)
Другие вопросы по тегам