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

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

Случай 1

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

Дело 2

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

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

Замечания:

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

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

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

UIApplication.shared.keyWindow?.endEditing(true)

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

        }

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

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

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

Ответ 9

Это очень просто, просто используйте эту установку pod, https://github.com/hackiftekhar/IQKeyboardManager

pod 'IQKeyboardManagerSwift', '6.3.0'

просто импортируйте IQKeyboardManagerSwift и добавьте один фрагмент кода в ваш AppDelegate (это решит проблему в глобальном масштабе в вашем приложении)

import IQKeyboardManagerSwift

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

  IQKeyboardManager.shared.enable = true

  return true
}
}