Comment masquer keyboard
en utilisant SwiftUI
pour les cas ci-dessous?
Cas 1
J'ai TextField
et je dois masquer le keyboard
lorsque l'utilisateur clique sur le bouton return
.
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
).
Vous pouvez forcer le premier intervenant à 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 en plein écran avec une action tap, qui déclenchera la 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 réponse de RyanTCB est bonne; voici quelques améliorations qui le rendent plus simple à utiliser 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)
devrait être correctement keyWindow?.endEditing(true)
(oui, vous pourriez dire que cela ne peut pas arriver.)
Plus intéressant est de savoir comment vous pouvez l'utiliser. Par exemple, supposons que vous ayez un formulaire contenant plusieurs champs modifiables. Enveloppez-le comme ceci:
Form {
.
.
.
}
.modifier(DismissingKeyboard())
Maintenant, appuyer sur n'importe quel contrôle qui ne présente pas lui-même de clavier fera le rejet approprié.
(Testé avec beta 7)
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 ...
Après de nombreuses tentatives, j'ai trouvé une solution qui (actuellement) ne bloque aucun contrôle - en ajoutant la reconnaissance des gestes à UIWindow
.
UITapGestureRecognizer
et de copier simplement l'étape 3:Créez une classe de reconnaissance de gestes personnalisée qui fonctionne avec toutes les touches:
class AnyGestureRecognizer: UIGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
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
ajouter 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)
Implémentez UIGestureRecognizerDelegate
pour permettre des contacts simultanés.
extension SceneDelegate: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Maintenant, n'importe quel clavier sur n'importe quelle vue sera fermé au toucher ou glissé vers l'extérieur.
P.S. Si vous ne souhaitez fermer que des TextFields spécifiques, ajoutez et supprimez la reconnaissance des gestes dans la fenêtre chaque fois qu’elle est appelée rappel de TextField onEditingChanged
J'ai trouvé une autre façon de supprimer le clavier qui ne nécessite pas d'accéder à la propriété keyWindow
; en fait, le compilateur renvoie un avertissement en utilisant
UIApplication.shared.keyWindow?.endEditing(true)
'keyWindow' est déconseillé dans iOS 13.0: ne doit pas être utilisé pour les applications qui prennent en charge plusieurs scènes car il renvoie une fenêtre de clé sur toutes les scènes connectées
Au lieu de cela, j'ai utilisé ce code:
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
J'ai vécu cela en utilisant un TextField dans un NavigationView. Ceci est ma solution pour cela. Il fermera 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)})
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)
}
Parce que keyWindow
est obsolète.
extension View {
func endEditing(_ force: Bool) {
UIApplication.shared.windows.forEach { $0.endEditing(force)}
}
}
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 rejeter le clavier sur d'autres gestes que onTapGesture
, vous pouvez spécifier ce que vous voulez dans la fonction appel.
// 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 pour 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))
}
}
Veuillez noter que si vous utilisez tous les gestes peuvent entrer en conflit et je n'ai trouvé aucune solution intéressante pour résoudre ce problème.
On dirait que la solution endEditing
est la seule comme @rraphael l'a souligné.
L'exemple le plus propre que j'ai vu jusqu'à présent est le suivant:
extension View {
func endEditing(_ force: Bool) {
UIApplication.shared.keyWindow?.endEditing(force)
}
}
puis l'utiliser dans le onCommit:
Je préfère utiliser la .onLongPressGesture(minimumDuration: 0)
, qui ne fait pas clignoter le clavier lorsqu'un autre TextView
est activé (effet secondaire de .onTapGesture
). Le code de masquage du clavier 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)
}
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()
}
}
Ma solution comment cacher le clavier logiciel lorsque les utilisateurs tapent à l'extérieur. Vous devez utiliser contentShape
avec onLongPressGesture
pour détecter l'intégralité du conteneur View. onTapGesture
requis pour éviter de bloquer le focus sur TextField
. Vous pouvez utiliser onTapGesture
au lieu de onLongPressGesture
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: {})
}
}
Cette méthode vous permet de masquer le clavier sur entretoises!
Ajoutez d'abord cette fonction (Crédit accordé à: Casper Zandbergen, de SwiftUI ne peut pas taper dans 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
}
}
}
Ajoutez ensuite les 2 fonctions suivantes (Crédit accordé à: rraphael, à partir 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, il vous suffit de vous référer à la première réponse ici de rraphael pour plus de détails.
private func endEditing() {
UIApplication.shared.endEditing()
}
Enfin, vous pouvez maintenant simplement appeler ...
Spacer().onTapGesture {
self.endEditing()
}
Ainsi, toute zone d'espacement fermera le clavier maintenant. Plus besoin d'une grande vue sur fond blanc!
Vous pouvez hypothétiquement appliquer cette technique de extension
à tous les contrôles dont vous avez besoin pour prendre en charge TapGestures qui ne le font pas actuellement et appeler la fonction onTapGesture
en combinaison avec self.endEditing()
pour fermer le clavier dans toutes les situations que vous désirez.
Basé sur la réponse de @ Sajjon, voici une solution vous permettant de supprimer le clavier au toucher, appui long, glisser, agrandir et faire pivoter les gestes selon votre choix.
Cette solution fonctionne dans XCode 11.4
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))
}
}