Schnelle Benutzeroberfläche So erstellen Sie TextField, das nur Zahlen akzeptiert

9

Ich bin neu in SwiftUI und iOS und versuche, ein Eingabefeld zu erstellen, das nur Zahlen akzeptiert

 TextField("Total number of people", text: $numOfPeople)

Das TextField erlaubt auch alphabetische Zeichen. Wie beschränke ich den Benutzer darauf, nur Zahlen einzugeben?

Lupyana Mbembati
quelle
Beachten Sie, dass TextField eine Init-Methode hat, die einen Formatierer als Argument verwendet
Joakim Danielson,
@JoakimDanielson Kannst du vielleicht helfen, zu zeigen, wie ich den Formatierer verwenden kann?
Lupyana Mbembati

Antworten:

10

Sie können den Tastaturtyp so einstellen, dass die Art der Tastatur eingeschränkt TextFieldwird.

TextField("Total number of people", text: $numOfPeople)
    .keyboardType(.numberPad)

Die Dokumentation von Apple finden Sie hier. Eine Liste aller unterstützten Tastaturtypen finden Sie hier .

Hinweis: Dies funktioniert auf einem iPad nicht, da für das iPad kein Nummernblock vorhanden ist. Für eine bessere Lösung überprüfen Sie die Lösung von John M unter https://stackoverflow.com/a/58736068/5508175

Andrew
quelle
1
Dies ist genau das, wonach ich gesucht habe 👍
Lupyana Mbembati
Ich habe ein kleines Problem, die Tastatur wird nicht verschwinden, wenn ich mit dem Tippen fertig bin. Irgendeine Idee warum?
Lupyana Mbembati
1
@LupyanaMbembati Diese SO-Frage / Antwort enthält mehrere Vorschläge, wie Sie die Tastatur ausblenden können, wenn Sie damit fertig sind. stackoverflow.com/questions/56491386/…
Andrew
1
Bitte beachten Sie, dass dies nicht auf dem iPad
funktioniert
3
Dies verhindert nicht die nicht numerische Eingabe. siehe meine Antwort.
John M.
23

Das Anzeigen eines Nummernblocks ist zwar ein guter erster Schritt, verhindert jedoch nicht die Eingabe fehlerhafter Daten:

  1. Der Benutzer kann nicht numerischen Text in das Textfeld einfügen
  2. iPad-Benutzer erhalten weiterhin eine vollständige Tastatur
  3. Jeder mit angeschlossener Bluetooth-Tastatur kann alles eingeben

Was Sie wirklich tun möchten, ist die Eingabe wie folgt zu bereinigen:

import SwiftUI
import Combine

struct StackOverflowTests: View {
    @State private var numOfPeople = "0"

    var body: some View {
        TextField("Total number of people", text: $numOfPeople)
            .keyboardType(.numberPad)
            .onReceive(Just(numOfPeople)) { newValue in
                let filtered = newValue.filter { "0123456789".contains($0) }
                if filtered != newValue {
                    self.numOfPeople = filtered
                }
        }
    }
}

Bei jeder numOfPeopleÄnderung werden die nicht numerischen Werte herausgefiltert und der gefilterte Wert verglichen, um festzustellen, ob er numOfPeopleein zweites Mal aktualisiert werden sollte, wobei die fehlerhafte Eingabe mit der gefilterten Eingabe überschrieben wird.

Beachten Sie, dass der JustHerausgeber dies von Ihnen verlangt import Combine.

BEARBEITEN:

Beachten Sie zur Erläuterung des JustHerausgebers den folgenden konzeptionellen Überblick darüber, was passiert, wenn Sie den Wert in ändern TextField:

  1. Da TextFielda Bindingbis a benötigt String, werden beim Ändern des Feldinhalts auch diese Änderungen in die @StateVariable zurückgeschrieben.
  2. Wenn sich eine markierte Variable @Stateändert, berechnet SwiftUI die bodyEigenschaft der Ansicht neu.
  3. Während der bodyBerechnung wird ein JustHerausgeber erstellt. Combine hat viele verschiedene Publisher, die im Laufe der Zeit Werte ausgeben, aber der JustPublisher nimmt "nur" einen einzelnen Wert (den neuen Wert von numberOfPeople) und gibt ihn auf Anfrage aus.
  4. Die onReceiveMethode macht einen ViewAbonnenten zu einem Herausgeber, in diesem Fall zu dem Justgerade erstellten Herausgeber. Nach dem Abonnieren werden sofort alle verfügbaren Werte vom Herausgeber abgefragt, von denen es nur einen gibt, den neuen Wert von numberOfPeople.
  5. Wenn der onReceiveTeilnehmer einen Wert erhält, führt er den angegebenen Abschluss aus. Unsere Schließung kann auf zwei Arten enden. Wenn der Text bereits nur numerisch ist, tut er nichts. Wenn der gefilterte Text unterschiedlich ist, wird er in die @StateVariable geschrieben, wodurch die Schleife erneut beginnt. Diesmal wird der Abschluss jedoch ausgeführt, ohne dass Eigenschaften geändert werden.

Weitere Informationen finden Sie unter Verwenden von Kombinieren .

John M.
quelle
Ich habe gerade angefangen, mich mit Swift / SwiftUI (aus der Windows C # - und Web Typescript-Welt) zu befassen, und das erste Problem, auf das ich stieß, war das Filtern von Benutzereingaben, um nur Zahlen zuzulassen. Es ist sehr üblich, Eingaben basierend auf einem Regex-Ausdruck zu filtern. Ihre Lösung scheint genau das zu sein, wonach ich gesucht habe - vielen Dank. Ich habe mir die Dokumentation angesehen und es scheint bestenfalls ein bisschen zu fehlen. Würde es Ihnen etwas ausmachen, den Grund für den 'Just'-Verlag zu erklären
Jesper Kristiansen
+1 für diese Antwort. Allerdings verstehe ich das Importieren von Combine dafür nicht ganz. Was macht gerade? Danke
Davidv
@JesperKristiansen @davidev Ich habe eine Erklärung des JustHerausgebers hinzugefügt .
John M.
Vielen Dank für diese nette Erklärung! Ihre Lösung funktioniert, aber für einen kurzen Moment können Sie den alten Wert sehen, bevor er gefiltert wird. Gibt es eine Möglichkeit, dies zu verhindern?
Lupurus
1
Übrigens: .onReceive wird jedes Mal aufgerufen, wenn etw. sonst wird in der Ansicht geändert. Ist das nicht ein bisschen zu schwer?
Lupurus
1

Sie müssen nicht verwenden Combineund onReceivekönnen auch diesen Code verwenden:

class Model: ObservableObject {
    @Published var text : String = ""
}

struct ContentView: View {

    @EnvironmentObject var model: Model

    var body: some View {
        TextField("enter a number ...", text: Binding(get: { self.model.text },
                                                      set: { self.model.text = $0.filter { "0123456789".contains($0) } }))
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(Model())
    }
}

Leider gibt es auch ein kleines Flackern, so dass Sie auch die nicht erlaubten Zeichen für eine sehr kurze Zeit sehen können (in meinen Augen etwas kürzer als der Weg mit Combine)

Lupurus
quelle
0

Ein anderer Ansatz besteht möglicherweise darin, eine Ansicht zu erstellen, die die TextField-Ansicht umschließt und zwei Werte enthält: eine private Variable, die den eingegebenen String enthält, und einen bindbaren Wert, der das Double-Äquivalent enthält. Jedes Mal, wenn der Benutzer ein Zeichen eingibt, versucht er, das Double zu aktualisieren.

Hier ist eine grundlegende Implementierung:

struct NumberEntryField : View {
    @State private var enteredValue : String = ""
    @Binding var value : Double

    var body: some View {        
        return TextField("", text: $enteredValue)
            .onReceive(Just(enteredValue)) { typedValue in
                if let newValue = Double(typedValue) {
                    self.value = newValue
                }
        }.onAppear(perform:{self.enteredValue = "\(self.value)"})
    }
}

Sie könnten es so verwenden:

struct MyView : View {
    @State var doubleValue : Double = 1.56

    var body: some View {        
        return HStack {
             Text("Numeric field:")
             NumberEntryField(value: self.$doubleValue)   
            }
      }
}

Dies ist ein einfaches Beispiel. Möglicherweise möchten Sie Funktionen hinzufügen, um eine Warnung für schlechte Eingaben anzuzeigen und möglicherweise Überprüfungen usw. zu begrenzen.

Philip Pegden
quelle