SwiftUI: cómo crear TextField que solo acepta números

13

Soy nuevo en SwiftUI e iOS, y estoy tratando de crear un campo de entrada que solo acepte números.

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

El TextFieldactualmente permite caracteres alfabéticos, ¿cómo puedo hacer para que el usuario sólo puede introducir números?

Lupyana Mbembati
fuente
Tenga en cuenta que TextField tiene un método de inicio que toma un formateador como argumento
Joakim Danielson, el
@JoakimDanielson, ¿podrías ayudarme a mostrar cómo puedo usar el formateador?
Lupyana Mbembati

Respuestas:

13

Puede configurar el tipo de teclado en el TextFieldque limitará lo que las personas pueden escribir.

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

La documentación de Apple se puede encontrar aquí , y puede ver una lista de todos los tipos de teclado compatibles aquí .

Nota: esto no funcionará en un iPad ya que no hay teclado numérico para el iPad. Para una mejor solución, consulte la solución de John M a continuación https://stackoverflow.com/a/58736068/5508175

Andrés
fuente
1
Esto es exactamente lo que estaba buscando 👍
Lupyana Mbembati
Estoy experimentando un problema menor, el teclado no desaparecerá cuando termine de escribir, ¿Alguna idea de por qué?
Lupyana Mbembati
1
@LupyanaMbembati esta pregunta / respuesta SO tiene varias sugerencias sobre cómo ocultar el teclado una vez que haya terminado con él. stackoverflow.com/questions/56491386/…
Andrew
1
Tenga en cuenta que esto no funcionará en iPad
LuLuGaGa
3
En realidad, esto no impide la entrada no numérica; mira mi respuesta
John M.
30

Aunque mostrar un teclado numérico es un buen primer paso, en realidad no evita que se ingresen datos incorrectos:

  1. El usuario puede pegar texto no numérico en el campo de texto.
  2. Los usuarios de iPad seguirán teniendo un teclado completo
  3. Cualquier persona con un teclado Bluetooth conectado puede escribir cualquier cosa

Lo que realmente quieres hacer es desinfectar la entrada, así:

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

Cada vez que numOfPeoplecambia, los valores no numéricos se filtran y el valor filtrado se compara para ver si numOfPeopledebe actualizarse por segunda vez, sobrescribiendo la entrada incorrecta con la entrada filtrada.

Tenga en cuenta que el Justeditor requiere que usted import Combine.

EDITAR:

Para explicar al Justeditor, considere el siguiente esquema conceptual de lo que ocurre cuando cambia el valor en TextField:

  1. Debido a que TextFieldtoma a Bindinga a String, cuando se cambia el contenido del campo, también escribe ese cambio nuevamente en la @Statevariable.
  2. Cuando una variable marcada @Statecambia, SwiftUI vuelve a calcular la bodypropiedad de la vista.
  3. Durante el bodycálculo, Justse crea un editor. Combine tiene muchos editores diferentes para emitir valores a lo largo del tiempo, pero el Justeditor toma "solo" un solo valor (el nuevo valor de numberOfPeople) y lo emite cuando se le solicita.
  4. El onReceivemétodo convierte a Viewun suscriptor en un editor, en este caso, el Justeditor que acabamos de crear. Una vez suscrito, solicita inmediatamente los valores disponibles del editor, de los cuales solo hay uno, el nuevo valor de numberOfPeople.
  5. Cuando el onReceivesuscriptor recibe un valor, ejecuta el cierre especificado. Nuestro cierre puede terminar de dos maneras. Si el texto ya es solo numérico, entonces no hace nada. Si el texto filtrado es diferente, se escribe en la @Statevariable, que comienza el ciclo nuevamente, pero esta vez el cierre se ejecutará sin modificar ninguna propiedad.

Consulte Uso de Combinar para obtener más información.

John M.
fuente
Recién comencé a buscar Swift / SwiftUI (proveniente de Windows C # y el mundo de Script mecanografiado web), y el primer problema con el que me topé fue cómo filtrar la entrada del usuario para permitir solo números. Es muy común filtrar la entrada en función de una expresión regular. Su solución parece ser exactamente lo que estaba buscando, muchas gracias. Miré la documentación y parece que falta un poco en el mejor de los casos. ¿Le importaría explicar el motivo de la editorial 'Just'
Jesper Kristiansen
+1 por esta respuesta. Sin embargo, no entiendo tranquilamente importar Combinar para eso. ¿Qué está haciendo? Gracias
Davidev
@JesperKristiansen @davidev Agregué una explicación del Justeditor.
John M.
Gracias por esa buena explicación! Su solución funciona, pero por un breve momento puede ver el valor anterior antes de que se filtre. ¿Hay alguna manera de prevenir esto?
Lupurus
1
Y por cierto: .onReceive se llama cada vez, cuando algo. más en la vista get's cambiado. ¿No es esto un poco pesado?
Lupurus
1

No necesita usar Combiney onReceive, también puede usar este código:

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

Desafortunadamente, también hay un pequeño parpadeo, por lo que también puede ver los caracteres no permitidos por un tiempo muy corto (en mi opinión, un poco más corto como el camino con Combine)

Lupurus
fuente
1

Otro enfoque quizás sea crear una Vista que envuelva la vista TextField y que contenga dos valores: una variable privada que contenga la Cadena ingresada y un valor enlazable que contenga el Doble equivalente. Cada vez que el usuario escribe un carácter, intenta actualizar el Doble.

Aquí hay una implementación básica:

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

Podrías usarlo así:

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

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

Este es un ejemplo básico: es posible que desee agregar funcionalidad para mostrar una advertencia por una entrada deficiente, y tal vez verificaciones de límites, etc.

Philip Pegden
fuente
0

La ViewModifierversión de la @ John M. respuesta .

import Combine
import SwiftUI

public struct NumberOnlyViewModifier: ViewModifier {

    @Binding var text: String

    public init(text: Binding<String>) {
        self._text = text
    }

    public func body(content: Content) -> some View {
        content
            .keyboardType(.numberPad)
            .onReceive(Just(text)) { newValue in
                let filtered = newValue.filter { "0123456789".contains($0) }
                if filtered != newValue {
                    self.text = filtered
                }
            }
    }
}
hstdt
fuente