Wie verstecke keyboard
ich mich mit SwiftUI
für die folgenden Fälle?
Fall 1
Ich habe TextField
und ich muss das ausblenden, keyboard
wenn der Benutzer auf die return
Schaltfläche klickt .
Fall 2
Ich habe TextField
und ich muss das verstecken, keyboard
wenn der Benutzer nach draußen tippt.
Wie kann ich das machen SwiftUI
?
Hinweis:
Ich habe keine Frage zu gestellt UITextField
. Ich möchte es mit tun SwifUI.TextField
.
Antworten:
Sie können den Rücktritt des Ersthelfers erzwingen, indem Sie eine Aktion an die freigegebene Anwendung senden:
extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
Jetzt können Sie diese Methode verwenden, um die Tastatur zu schließen, wann immer Sie möchten:
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() } } } }
Wenn Sie die Tastatur durch Tippen schließen möchten, können Sie eine weiße Vollbildansicht mit einem Tippen erstellen, die Folgendes auslöst
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() } }
quelle
.keyWindow
ist jetzt veraltet. Siehe Lorenzo Santinis Antwort ..tapAction
wurde umbenannt in.onTapGesture
Nach vielen Versuchen habe ich eine Lösung gefunden, die (derzeit) keine Steuerelemente blockiert - das Hinzufügen eines Gestenerkenners
UIWindow
.UITapGestureRecognizer
nur Schritt 3 zu verwenden und zu kopieren:Erstellen Sie eine benutzerdefinierte Gestenerkennungsklasse, die mit allen Berührungen funktioniert:
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 } }
In
SceneDelegate.swift
in derfunc scene
fügen nächsten Code: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)
Implementieren
UIGestureRecognizerDelegate
, um gleichzeitige Berührungen zu ermöglichen.extension SceneDelegate: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } }
Jetzt wird jede Tastatur in einer beliebigen Ansicht geschlossen, wenn Sie sie berühren oder nach draußen ziehen.
PS Wenn Sie nur bestimmte TextFields schließen möchten, fügen Sie dem Fenster eine Gestenerkennung hinzu und entfernen Sie sie, wenn Sie den Rückruf von TextField aufrufen
onEditingChanged
quelle
@ RyanTCBs Antwort ist gut; Hier sind einige Verbesserungen, die die Verwendung vereinfachen und einen möglichen Absturz vermeiden:
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) } } }
Die 'Bugfix' ist einfach so, wie es
keyWindow!.endEditing(true)
sein solltekeyWindow?.endEditing(true)
(ja, Sie könnten argumentieren, dass es nicht passieren kann.)Interessanter ist, wie Sie es verwenden können. Angenommen, Sie haben ein Formular mit mehreren bearbeitbaren Feldern. Wickeln Sie es einfach so ein:
Form { . . . } .modifier(DismissingKeyboard())
Wenn Sie nun auf ein Steuerelement tippen, das selbst keine Tastatur enthält, wird dies entsprechend verworfen.
(Getestet mit Beta 7)
quelle
Can't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
Ich habe dies bei der Verwendung eines TextFields in einer Navigationsansicht erlebt. Das ist meine Lösung dafür. Die Tastatur wird geschlossen, wenn Sie mit dem Scrollen beginnen.
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)})
quelle
Ich habe eine andere Möglichkeit gefunden, die Tastatur zu schließen, für die kein Zugriff auf die
keyWindow
Eigenschaft erforderlich ist . Tatsächlich gibt der Compiler mit eine Warnung zurückUIApplication.shared.keyWindow?.endEditing(true)
Stattdessen habe ich diesen Code verwendet:
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
quelle
SwiftUI in der Datei 'SceneDelegate.swift' fügen Sie einfach Folgendes hinzu: .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() } }
Dies reicht für jede Ansicht über die Tastatur in Ihrer App ...
quelle
SwiftUI 2
Hier ist eine aktualisierte Lösung für SwiftUI 2 / iOS 14 (ursprünglich hier von Mikhail vorgeschlagen ).
Wenn Sie den SwiftUI-Lebenszyklus verwenden, werden weder die
AppDelegate
noch dieSceneDelegate
fehlenden verwendet:@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 } }
Hier ist ein Beispiel, wie simultane Gesten mit Ausnahme von Long Press-Gesten erkannt werden:
extension UIApplication: UIGestureRecognizerDelegate { public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return !otherGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) } }
quelle
return false
.Meine Lösung zum Ausblenden der Softwaretastatur, wenn Benutzer nach draußen tippen. Sie verwenden müssen
contentShape
mitonLongPressGesture
den gesamten View - Container zu erfassen.onTapGesture
erforderlich, um zu vermeiden, dass der Fokus blockiert wirdTextField
. Sie könnenonTapGesture
anstelle von verwenden,onLongPressGesture
aber NavigationBar-Elemente funktionieren nicht.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: {}) } }
quelle
Fügen Sie diesen Modifikator der Ansicht hinzu, in der Sie Benutzerabgriffe erkennen möchten
.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) }
quelle
Ich bevorzuge die Verwendung von
.onLongPressGesture(minimumDuration: 0)
, wodurch die Tastatur nicht blinkt, wenn eine andereTextView
aktiviert wird (Nebeneffekt von.onTapGesture
). Der Tastaturcode zum Ausblenden kann eine wiederverwendbare Funktion sein..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) }
quelle
Weil
keyWindow
veraltet ist.extension View { func endEditing(_ force: Bool) { UIApplication.shared.windows.forEach { $0.endEditing(force)} } }
quelle
force
Parameter wird nicht verwendet. Es sollte sein{ $0.endEditing(force)}
Scheint, als wäre die
endEditing
Lösung die einzige, auf die @rraphael hingewiesen hat.Das sauberste Beispiel, das ich bisher gesehen habe, ist folgendes:
extension View { func endEditing(_ force: Bool) { UIApplication.shared.keyWindow?.endEditing(force) } }
und dann mit dem in der
onCommit:
quelle
.keyWindow
ist jetzt veraltet. Siehe Lorenzo Santinis Antwort .Wenn Sie die Antwort von @Feldur (die auf @ RyanTCBs basiert) erweitern, finden Sie hier eine noch ausdrucksstärkere und leistungsstärkere Lösung, mit der Sie die Tastatur bei anderen Gesten schließen
onTapGesture
können, als Sie im Funktionsaufruf angeben können, welche Sie möchten.Verwendung
// MARK: - View extension RestoreAccountInputMnemonicScreen: View { var body: some View { List(viewModel.inputWords) { inputMnemonicWord in InputMnemonicCell(mnemonicInput: inputMnemonicWord) } .dismissKeyboard(on: [.tap, .drag]) } }
Oder mit
All.gestures
(nur Zucker fürGestures.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)) } }
Wort der Vorsicht
Bitte beachten Sie, dass bei Verwendung aller Gesten Konflikte auftreten können und ich keine saubere Lösung für dieses Problem gefunden habe.
quelle
eraseToAny()
Mit dieser Methode können Sie die Tastatur auf Abstandshaltern ausblenden !
Fügen Sie zuerst diese Funktion hinzu (Gutschrift an: Casper Zandbergen, von SwiftUI kann Spacer of HStack nicht antippen )
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 } } }
Fügen Sie als nächstes die folgenden 2 Funktionen hinzu (Gutschrift an: rraphael, aus dieser Frage)
extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
Die folgende Funktion wird Ihrer View-Klasse hinzugefügt. Weitere Informationen finden Sie in der Top-Antwort von rraphael.
private func endEditing() { UIApplication.shared.endEditing() }
Endlich können Sie jetzt einfach anrufen ...
Spacer().onTapGesture { self.endEditing() }
Dadurch wird jeder Abstandhalterbereich jetzt die Tastatur schließen. Keine große weiße Hintergrundansicht mehr erforderlich!
Sie können diese Technik hypothetisch
extension
auf alle Steuerelemente anwenden , die Sie zur Unterstützung von TapGestures benötigen, die dies derzeit nicht tun, und dieonTapGesture
Funktion in Kombination mit aufrufenself.endEditing()
, um die Tastatur in jeder gewünschten Situation zu schließen.quelle
Bitte überprüfen Sie https://github.com/michaelhenry/KeyboardAvoider
Fügen Sie einfach
KeyboardAvoider {}
oben in Ihre Hauptansicht ein und das ist alles.KeyboardAvoider { VStack { TextField() TextField() TextField() TextField() } }
quelle
Basierend auf der Antwort von @ Sajjon ist hier eine Lösung, mit der Sie die Tastatur nach Belieben durch Tippen, langes Drücken, Ziehen, Vergrößern und Drehen schließen können.
Diese Lösung funktioniert in XCode 11.4
Verwendung, um das von @IMHiteshSurani angeforderte Verhalten abzurufen
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)) } }
quelle
Sie können die Interaktion mit UIKit vollständig vermeiden und in reinem SwiftUI implementieren . Fügen Sie einfach einen
.id(<your id>)
Modifikator zu Ihrem hinzuTextField
und ändern Sie seinen Wert, wann immer Sie die Tastatur schließen möchten (beim Streichen, Tippen auf Ansicht, Tastenaktion, ..).Beispielimplementierung:
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 }) } } }
Beachten Sie, dass ich es nur in der neuesten Xcode 12 Beta getestet habe, es aber problemlos mit älteren Versionen (sogar Xcode 11) funktionieren sollte.
quelle
Tastatur
Return
KeyZusätzlich zu allen Antworten zum Tippen außerhalb des Textfelds möchten Sie möglicherweise die Tastatur schließen, wenn der Benutzer auf die Eingabetaste auf der Tastatur tippt:
Definieren Sie diese globale Funktion:
func resignFirstResponder() { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) }
Und fügen Sie Verwendung in
onCommit
Argument es hinzu:TextField("title", text: $text, onCommit: { resignFirstResponder() })
Leistungen
Demo
quelle
Bisher haben die oben genannten Optionen bei mir nicht funktioniert, da ich Formular- und Insider-Schaltflächen, Links, Auswahl ...
Ich erstelle den folgenden Code, der funktioniert, mit Hilfe der obigen Beispiele.
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()) } }
Verwendung:
var body: some View { NavigationView { Form { picker button textfield text } .dismissingKeyboard()
quelle
SwiftUI wurde im Juni / 2020 mit Xcode 12 und iOS 14 veröffentlicht und fügt den Modifikator hideKeyboardOnTap () hinzu. Dies sollte Ihre Fallnummer 2 lösen. Die Lösung für Ihre Fallnummer 1 ist mit Xcode 12 und iOS 14 kostenlos: Die Standardtastatur für TextField wird automatisch ausgeblendet, wenn die Eingabetaste gedrückt wird.
quelle