Wie verstecke ich die Tastatur bei Verwendung von SwiftUI?

88

Wie verstecke keyboardich mich mit SwiftUIfür die folgenden Fälle?

Fall 1

Ich habe TextFieldund ich muss das ausblenden, keyboardwenn der Benutzer auf die returnSchaltfläche klickt .

Fall 2

Ich habe TextFieldund ich muss das verstecken, keyboardwenn 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.

Hitesh Surani
quelle
29
@DannyBuonocore Lies meine Frage noch einmal sorgfältig durch!
Hitesh Surani
9
@DannyBuonocore Dies ist kein Duplikat der genannten Frage. Diese Frage bezieht sich auf SwiftUI, und andere ist normal UIKit
Johnykutty
1
@DannyBuonocore Bitte haben Sie unter developer.apple.com/documentation/swiftui nach dem Unterschied zwischen UIKit und SwiftUI gesucht. Vielen Dank
Hitesh Surani
Ich habe meine Lösung hier hinzugefügt. Ich hoffe, sie hilft Ihnen.
Victor Kushnerov
Die meisten Lösungen funktionieren hier nicht wie gewünscht, da sie die gewünschten Reaktionen auf andere Kontrollabgriffe deaktivieren. Eine funktionierende Lösung finden Sie hier: forums.developer.apple.com/thread/127196
Hardy

Antworten:

79

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()
    }
}
Rraphael
quelle
1
.keyWindowist jetzt veraltet. Siehe Lorenzo Santinis Antwort .
LinusGeffarth
3
Auch .tapActionwurde umbenannt in.onTapGesture
LinusGeffarth
Kann die Tastatur geschlossen werden, wenn ein alternatives Steuerelement aktiv wird? stackoverflow.com/questions/58643512/…
Yarm
1
Gibt es eine Möglichkeit, dies ohne den Whitebackground zu tun? Ich verwende Abstandshalter und benötige sie, um eine Tippgeste auf dem Abstandshalter zu erkennen. Auch die Strategie des weißen Hintergrunds verursacht ein Problem bei den neueren iPhones, bei denen oben zusätzlicher Bildschirmplatz vorhanden ist. Jede Hilfe geschätzt!
Joseph Astrahan
Ich habe eine Antwort gepostet, die Ihr Design verbessert. Fühlen Sie sich frei, Änderungen an Ihrer Antwort vorzunehmen, wenn Sie möchten, dass mir die Gutschrift egal ist.
Joseph Astrahan
61

Nach vielen Versuchen habe ich eine Lösung gefunden, die (derzeit) keine Steuerelemente blockiert - das Hinzufügen eines Gestenerkenners UIWindow.

  1. Wenn Sie die Tastatur nur bei Tippen nach außen schließen möchten (ohne mit Verzögerungen umzugehen), reicht es aus, UITapGestureRecognizernur Schritt 3 zu verwenden und zu kopieren:
  2. 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
        }
    }
    
  3. In SceneDelegate.swiftin der func scenefü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)  
    
  4. 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

Mikhail
quelle
3
Diese Antwort sollte oben stehen. Andere Antworten schlagen fehl, wenn andere Steuerelemente in der Ansicht vorhanden sind.
Imthath
1
@ RolandLariotte aktualisierte Antwort, um dieses Verhalten zu beheben, schauen Sie sich die neue Implementierung von AnyGestureRecognizer
Mikhail
1
Tolle Antwort. Funktioniert einwandfrei. @Mikhail ist tatsächlich daran interessiert zu wissen, wie Sie den Gestenerkenner speziell für einige Textfelder entfernen (ich habe eine automatische Vervollständigung mit Tags erstellt, sodass ich nicht möchte, dass dieses bestimmte Textfeld jedes Mal den Fokus verliert, wenn ich auf ein Element in der Liste tippe)
Pasta
1
Diese Lösung ist eigentlich großartig, aber nachdem ich sie 3 Monate lang verwendet habe, habe ich leider einen Fehler gefunden, der direkt durch diese Art von Hack verursacht wurde. Bitte beachten Sie, dass Ihnen dasselbe passiert
glassomoss
1
Fantastische Antwort! Ich frage mich, wie dies mit iOS 14 ohne das Szenedelegate implementiert wird.
Dom
28

@ 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 sollte keyWindow?.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)

Feldur
quelle
6
Hmmm - Tippen auf andere Bedienelemente registriert nicht mehr. Die Veranstaltung wird verschluckt.
Yarm
Ich kann das nicht replizieren - es funktioniert immer noch für mich mit den neuesten Tropfen von Apple ab dem 1. November. Hat es funktioniert und dann einfach aufgehört für dich zu arbeiten, oder ??
Feldur
Wenn Sie einen DatePicker im Formular haben, wird der DatePicker nicht mehr angezeigt
Albert
@ Albert - das stimmt; Um diesen Ansatz zu verwenden, müssen Sie die mit DismissingKeyboard () dekorierten Elemente auf eine feinere Ebene aufteilen, die für die Elemente gilt, die den DatePicker schließen und vermeiden sollen.
Feldur
Die Verwendung dieses Codes reproduziert die WarnungCan't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
np2314
23

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)})
DubluDe
quelle
Dies führt onDelete (Wischen zum Löschen) zu einem seltsamen Verhalten.
Tarek Hallak
Das ist schön, aber was ist mit dem Wasserhahn?
Danny182
20

Ich habe eine andere Möglichkeit gefunden, die Tastatur zu schließen, für die kein Zugriff auf die keyWindowEigenschaft erforderlich ist . Tatsächlich gibt der Compiler mit eine Warnung zurück

UIApplication.shared.keyWindow?.endEditing(true)

'keyWindow' war in iOS 13.0 veraltet: Sollte nicht für Anwendungen verwendet werden, die mehrere Szenen unterstützen, da ein Schlüsselfenster für alle verbundenen Szenen zurückgegeben wird

Stattdessen habe ich diesen Code verwendet:

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

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

Dim Novo
quelle
4
Dies gibt ein weiteres Problem - ich habe eine Auswahl im Formular {} neben dem Textfeld, es reagierte nicht mehr. Ich habe keine Lösung dafür gefunden, indem ich alle Antworten in diesem Thema verwendet habe. Aber Ihre Antwort ist gut, um die Tastatur mit einem Fingertipp an einer anderen Stelle zu schließen - wenn Sie keine Picker verwenden.
Nalov
Hallo. Mein Code `` `var body: einige View {NavigationView {Form {Section {TextField (" typesomething ", Text: $ c)} Section {Picker (" name ", Auswahl: $ sel) {ForEach (0 .. <200 ) {Text ("(self.array [$ 0])%")}}} `` `Die Tastatur wird beim Tippen auf eine andere Stelle geschlossen, aber die Auswahl reagiert nicht mehr. Ich habe keinen Weg gefunden, es zum Laufen zu bringen.
Nalov
2
Hallo nochmal, im Moment habe ich zwei Lösungen: Die erste besteht darin, die native Tastatur zu verwenden, die auf der Eingabetaste deaktiviert ist, die zweite darin, die Handhabung des Klopfens geringfügig zu ändern (auch bekannt als 'костыль') - window.rootViewController = UIHostingController (rootView : contentView.onTapGesture (Anzahl: 2, durchführen: {window.endEditing (true)})) Hoffe das hilft dir ...
Dim Novo
Hallo. Vielen Dank. Der zweite Weg löste es. Ich verwende einen Ziffernblock, damit Benutzer nur Zahlen eingeben können, er hat keine Eingabetaste. Mit Klopfen abzulehnen war das, wonach ich suchte.
Nalov
Dies führt dazu, dass die Liste nicht navigiert werden kann.
Cui Mingda
14

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 AppDelegatenoch die SceneDelegatefehlenden 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)
    }
}
pawello2222
quelle
2
Das funktioniert perfekt! Vielen Dank für die Lösung
NotAPhoenix
2
Dies sollte im Vordergrund stehen, da der neue SwiftUI-Lebenszyklus berücksichtigt wird.
Carlosobedgomez
Das funktioniert super. Wenn ich jedoch zweimal in ein Textfeld tippe, verschwindet die Tastatur jetzt, anstatt den Text auszuwählen. Irgendeine Idee, wie ich doppeltes Tippen zur Auswahl zulassen kann?
Gary,
@Gary In der unteren Erweiterung sehen Sie die Zeile mit dem Kommentar false, wenn Sie während anderer Gesten kein Tippen erkennen möchten . Stellen Sie es einfach auf ein return false.
pawello2222
Das Setzen auf false funktioniert, aber die Tastatur wird auch nicht geschlossen, wenn jemand lange außerhalb des Textbereichs drückt oder zieht oder einen Bildlauf durchführt. Gibt es eine Möglichkeit, es nur für Doppelklicks auf false zu setzen (vorzugsweise Doppelklicks innerhalb des Textfelds, aber auch nur alle Doppelklicks würden ausreichen).
Gary
11

Meine Lösung zum Ausblenden der Softwaretastatur, wenn Benutzer nach draußen tippen. Sie verwenden müssen contentShapemit onLongPressGestureden gesamten View - Container zu erfassen. onTapGestureerforderlich, um zu vermeiden, dass der Fokus blockiert wird TextField. Sie können onTapGestureanstelle von verwenden, onLongPressGestureaber 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: {})
    }
}
Victor Kushnerov
quelle
Das hat super geklappt, ich habe es etwas anders benutzt und musste sicher sein, dass es im Haupt-Thread aufgerufen wurde.
keegan3d
7

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)

        }
RyanTCB
quelle
7

Ich bevorzuge die Verwendung von .onLongPressGesture(minimumDuration: 0), wodurch die Tastatur nicht blinkt, wenn eine andere TextViewaktiviert 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)
}
George Valkov
quelle
Immer noch ein Flackern mit dieser Methode.
Daniel Ryan
Das hat super geklappt, ich habe es etwas anders benutzt und musste sicher sein, dass es im Haupt-Thread aufgerufen wurde.
keegan3d
6

Weil keyWindowveraltet ist.

extension View {
    func endEditing(_ force: Bool) {
        UIApplication.shared.windows.forEach { $0.endEditing(force)}
    }
}
msk
quelle
1
Der forceParameter wird nicht verwendet. Es sollte sein{ $0.endEditing(force)}
Davide
5

Scheint, als wäre die endEditingLö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:

zero3nna
quelle
2
.keyWindowist jetzt veraltet. Siehe Lorenzo Santinis Antwort .
LinusGeffarth
Es wird auf iOS 13+
Ahmadreza
4

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 onTapGesturekö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ür Gestures.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.

Sajjon
quelle
Was ist gemeint mit eraseToAny()
Ravindra_Bhati
2

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 extensionauf alle Steuerelemente anwenden , die Sie zur Unterstützung von TapGestures benötigen, die dies derzeit nicht tun, und die onTapGestureFunktion in Kombination mit aufrufen self.endEditing(), um die Tastatur in jeder gewünschten Situation zu schließen.

Joseph Astrahan
quelle
Meine Frage ist nun, wie Sie ein Commit für ein Textfeld auslösen, wenn die Tastatur auf diese Weise ausgeblendet wird. Derzeit wird das Festschreiben nur ausgelöst, wenn Sie die Eingabetaste auf der iOS-Tastatur drücken.
Joseph Astrahan
2

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

}
Michael Henry
quelle
Dies funktioniert nicht für eine Formularansicht mit Textfeldern. Formular wird nicht angezeigt.
Nils
2

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))
    }
}
Nicolas Mandica
quelle
2

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 hinzu TextFieldund ä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.

josefdolezal
quelle
0

Tastatur ReturnKey

Zusä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 onCommitArgument es hinzu:

TextField("title", text: $text, onCommit:  {
    resignFirstResponder()
})

Leistungen

  • Sie können es von überall aus anrufen
  • Es ist nicht abhängig von UIKit oder SwiftUI (kann in Mac-Apps verwendet werden)
  • Es funktioniert sogar in iOS 13

Demo

Demo

Mojtaba Hosseini
quelle
0

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()
zdravko zdravkin
quelle
-2

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.

Marcel Mendes Filho
quelle
1
Es gibt keinen hideKeyboardOnTap-Modifikator in iOS14
Teo Sartori