Comment se cacher en keyboard
utilisant SwiftUI
pour les cas ci-dessous?
Cas 1
J'ai TextField
et je dois masquer le keyboard
lorsque l'utilisateur clique sur le return
bouton.
Cas 2
J'ai TextField
et je dois masquer le keyboard
lorsque l'utilisateur tape à l'extérieur.
Comment puis-je faire cela en utilisant SwiftUI
?
Remarque:
Je n'ai pas posé de question concernant UITextField
. Je veux le faire en utilisant SwifUI.TextField
.
Réponses:
Vous pouvez forcer le premier répondeur à démissionner en envoyant une action à l'application partagée:
extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
Vous pouvez maintenant utiliser cette méthode pour fermer le clavier quand vous le souhaitez:
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() } } } }
Si vous souhaitez fermer le clavier avec un tap out, vous pouvez créer une vue blanche plein écran avec une action tap, qui déclenchera le
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() } }
la source
.keyWindow
est désormais obsolète. Voir la réponse de Lorenzo Santini ..tapAction
a été renommé en.onTapGesture
Après de nombreuses tentatives, j'ai trouvé une solution qui (actuellement) ne bloque aucun contrôle - l'ajout de reconnaissance de gestes à
UIWindow
.UITapGestureRecognizer
et simplement copier l'étape 3:Créez une classe de reconnaissance de mouvement personnalisée qui fonctionne avec toutes les touches:
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 } }
Dans
SceneDelegate.swift
dans lefunc scene
, ajoutez le code suivant: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)
Mettre
UIGestureRecognizerDelegate
en œuvre pour permettre des touches simultanées.extension SceneDelegate: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } }
Désormais, n'importe quel clavier de n'importe quelle vue sera fermé au toucher ou en le faisant glisser à l'extérieur.
PS Si vous souhaitez fermer uniquement des TextFields spécifiques - ajoutez et supprimez le module de reconnaissance de gestes à la fenêtre chaque fois que vous êtes appelé rappel de TextField
onEditingChanged
la source
La réponse de @ RyanTCB est bonne; voici quelques améliorations qui simplifient l'utilisation et évitent un crash potentiel:
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) } } }
La 'correction de bogue' est simplement que
keyWindow!.endEditing(true)
correctement devrait êtrekeyWindow?.endEditing(true)
(oui, vous pourriez dire que cela ne peut pas arriver.)Plus intéressant, c'est comment vous pouvez l'utiliser. Par exemple, supposons que vous ayez un formulaire contenant plusieurs champs modifiables. Emballez-le simplement comme ceci:
Form { . . . } .modifier(DismissingKeyboard())
Maintenant, en tapant sur n'importe quel contrôle qui ne présente pas de clavier lui-même, le rejet sera approprié.
(Testé avec beta 7)
la source
Can't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
J'ai vécu cela en utilisant un TextField dans un NavigationView. C'est ma solution pour ça. Il supprimera le clavier lorsque vous commencerez à faire défiler.
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)})
la source
J'ai trouvé un autre moyen de supprimer le clavier qui ne nécessite pas d'accéder à la
keyWindow
propriété; en fait, le compilateur renvoie un avertissement en utilisantUIApplication.shared.keyWindow?.endEditing(true)
Au lieu de cela, j'ai utilisé ce code:
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
la source
SwiftUI dans le fichier 'SceneDelegate.swift' ajoutez simplement: .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() } }
cela suffit pour chaque vue utilisant le clavier de votre application ...
la source
SwiftUI 2
Voici une solution mise à jour pour SwiftUI 2 / iOS 14 (initialement proposée ici par Mikhail).
Il n'utilise
AppDelegate
ni le ni leSceneDelegate
qui sont manquants si vous utilisez le cycle de vie 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 } }
Voici un exemple de détection de gestes simultanés à l'exception des gestes Long Press:
extension UIApplication: UIGestureRecognizerDelegate { public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return !otherGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) } }
la source
return false
.Ma solution comment masquer le clavier logiciel lorsque les utilisateurs tapent à l'extérieur. Vous devez utiliser
contentShape
aveconLongPressGesture
pour détecter l'ensemble du conteneur View.onTapGesture
nécessaire pour éviter de bloquer la mise au point surTextField
. Vous pouvez utiliser à laonTapGesture
place deonLongPressGesture
mais les éléments NavigationBar ne fonctionneront pas.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: {}) } }
la source
ajoutez ce modificateur à la vue que vous souhaitez détecter les tapotements des utilisateurs
.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) }
la source
Je préfère utiliser le
.onLongPressGesture(minimumDuration: 0)
, qui ne fait pas clignoter le clavier lorsqu'un autreTextView
est activé (effet secondaire de.onTapGesture
). Le code de clavier de masquage peut être une fonction réutilisable..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) }
la source
Parce que
keyWindow
c'est obsolète.extension View { func endEditing(_ force: Bool) { UIApplication.shared.windows.forEach { $0.endEditing(force)} } }
la source
force
paramètre n'est pas utilisé. Ça devrait être{ $0.endEditing(force)}
On dirait que la
endEditing
solution est la seule comme l'a souligné @rraphael.L'exemple le plus clair que j'ai vu jusqu'à présent est celui-ci:
extension View { func endEditing(_ force: Bool) { UIApplication.shared.keyWindow?.endEditing(force) } }
puis l'utiliser dans le
onCommit:
la source
.keyWindow
est désormais obsolète. Voir la réponse de Lorenzo Santini .En développant la réponse de @Feldur (qui était basée sur @ RyanTCB), voici une solution encore plus expressive et puissante vous permettant de supprimer le clavier sur d'autres gestes que
onTapGesture
, vous pouvez spécifier ce que vous voulez dans l'appel de fonction.Usage
// MARK: - View extension RestoreAccountInputMnemonicScreen: View { var body: some View { List(viewModel.inputWords) { inputMnemonicWord in InputMnemonicCell(mnemonicInput: inputMnemonicWord) } .dismissKeyboard(on: [.tap, .drag]) } }
Ou en utilisant
All.gestures
(juste du sucre pourGestures.allCases
🍬).dismissKeyboard(on: All.gestures)
Code
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)) } }
Un mot d'avertissement
Veuillez noter que si vous utilisez tous les gestes, ils pourraient entrer en conflit et je n'ai pas trouvé de solution intéressante pour résoudre ce problème.
la source
eraseToAny()
Cette méthode vous permet de masquer le clavier sur les entretoises!
Ajoutez d'abord cette fonction (Crédit accordé à: Casper Zandbergen, de SwiftUI ne peut pas appuyer sur 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 } } }
Ensuite, ajoutez les 2 fonctions suivantes (Crédit accordé à: rraphael, de cette question)
extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
La fonction ci-dessous serait ajoutée à votre classe View, reportez-vous simplement à la première réponse de rraphael pour plus de détails.
private func endEditing() { UIApplication.shared.endEditing() }
Enfin, vous pouvez maintenant simplement appeler ...
Spacer().onTapGesture { self.endEditing() }
Cela permettra à toute zone d'espacement de fermer le clavier maintenant. Plus besoin d'une grande vue de fond blanc!
Vous pouvez hypothétiquement appliquer cette technique
extension
à toutes les commandes dont vous avez besoin pour prendre en charge TapGestures qui ne le font pas actuellement et appeler laonTapGesture
fonction en combinaison avecself.endEditing()
pour fermer le clavier dans n'importe quelle situation que vous souhaitez.la source
Veuillez vérifier https://github.com/michaelhenry/KeyboardAvoider
Incluez simplement
KeyboardAvoider {}
en haut de votre vue principale et c'est tout.KeyboardAvoider { VStack { TextField() TextField() TextField() TextField() } }
la source
Sur la base de la réponse de @ Sajjon, voici une solution vous permettant de supprimer les gestes du clavier au toucher, appui long, glissement, grossissement et rotation selon votre choix.
Cette solution fonctionne dans XCode 11.4
Utilisation pour obtenir le comportement demandé par @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) } }
Code
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)) } }
la source
Vous pouvez éviter complètement l'interaction avec UIKit et l'implémenter dans SwiftUI pur . Ajoutez simplement un
.id(<your id>)
modificateur à votreTextField
et modifiez sa valeur chaque fois que vous souhaitez ignorer le clavier (en faisant glisser, appuyez sur la vue, action sur le bouton, ..).Exemple d'implémentation:
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 }) } } }
Notez que je ne l'ai testé que dans la dernière version bêta de Xcode 12, mais cela devrait fonctionner avec les anciennes versions (même Xcode 11) sans aucun problème.
la source
Return
Touche du clavierEn plus de toutes les réponses sur le fait d'appuyer en dehors de textField, vous souhaiterez peut-être fermer le clavier lorsque l'utilisateur appuie sur la touche de retour du clavier:
définir cette fonction globale:
func resignFirstResponder() { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) }
Et ajoutez l'utilisation en
onCommit
argument:TextField("title", text: $text, onCommit: { resignFirstResponder() })
Avantages
Démo
la source
Jusqu'à présent, les options ci-dessus ne fonctionnaient pas pour moi, car j'ai un formulaire et des boutons internes, des liens, un sélecteur ...
Je crée ci-dessous le code qui fonctionne, avec l'aide des exemples ci-dessus.
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()) } }
Usage:
var body: some View { NavigationView { Form { picker button textfield text } .dismissingKeyboard()
la source
SwiftUI publié en juin / 2020 avec Xcode 12 et iOS 14 ajoute le modificateur hideKeyboardOnTap (). Cela devrait résoudre votre cas numéro 2. La solution pour votre cas numéro 1 est fournie gratuitement avec Xcode 12 et iOS 14: le clavier par défaut de TextField se masque automatiquement lorsque le bouton Retour est enfoncé.
la source