Interface do usuário Swift Como criar o TextField que aceita apenas números

10

Sou novo no swiftUI e iOs, estou tentando criar um campo de entrada que aceite apenas números

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

o TextField também permite caracteres alfabéticos, como limite o usuário a inserir apenas números?

Lupyana Mbembati
fonte
Note-se que TextField tem uma methid inicialização que leva um Formatter como argumento
Joakim Danielson
@JoakimDanielson, você pode ajudar a mostrar como eu posso usar o formattter?
Lupyana Mbembati

Respostas:

11

Você pode definir o tipo de teclado no TextFieldqual limitará o que as pessoas podem digitar.

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

A documentação da Apple pode ser encontrada aqui e você pode ver uma lista de todos os tipos de teclado suportados aqui .

Nota: isso não funcionará em um iPad, pois não há teclado numérico para o iPad. Para uma solução melhor, confira a solução de John M abaixo https://stackoverflow.com/a/58736068/5508175

Andrew
fonte
1
Este é exatamente o que eu estava procurando 👍
Lupyana Mbembati
Estou com um problema menor, o teclado não desaparece quando terminar de digitar. Alguma idéia do porquê?
Lupyana Mbembati
1
@LupyanaMbembati, esta pergunta / resposta do SO tem várias sugestões sobre como ocultar o teclado depois de terminar. stackoverflow.com/questions/56491386/...
Andrew
1
Observe que isso não funcionará no iPad
LuLuGaGa 06/11/19
3
Na verdade, isso não impede a entrada não numérica; veja minha resposta.
John M.
23

Embora mostrar um teclado numérico seja uma boa primeira etapa, na verdade não impede que dados ruins sejam inseridos:

  1. O usuário pode colar texto não numérico no campo de texto
  2. Os usuários do iPad ainda receberão um teclado completo
  3. Qualquer pessoa com um teclado Bluetooth conectado pode digitar qualquer coisa

O que você realmente quer fazer é desinfetar a entrada, assim:

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

Sempre que numOfPeoplemuda, os valores não numéricos são filtrados e o valor filtrado é comparado para ver se numOfPeopledeve ser atualizado uma segunda vez, substituindo a entrada incorreta pela entrada filtrada.

Observe que o Justeditor exige que você import Combine.

EDITAR:

Para explicar o Just editor, considere o seguinte esboço conceitual do que ocorre quando você altera o valor em TextField:

  1. Porque TextFieldleva um Bindinga umString , quando o conteúdo do campo é alterado, ele também grava essa mudança de volta na @Statevariável.
  2. Quando uma variável marcada @State alterada, o SwiftUI recalcula a bodypropriedade da exibição.
  3. Durante o bodycálculo, um Justeditor é criado. Combine tem muitos editores diferentes para emitir valores ao longo do tempo, mas oJust editor obtém "apenas" um valor único (o novo valor de numberOfPeople) e o emite quando solicitado.
  4. O onReceivemétodo torna um Viewassinante um editor, nesse caso, oJust editor que acabamos de criar. Uma vez inscrito, ele solicita imediatamente quaisquer valores disponíveis ao editor, dos quais existe apenas um, o novo valor de numberOfPeople.
  5. Quando o onReceiveassinante recebe um valor, ele executa o fechamento especificado. Nosso fechamento pode terminar de duas maneiras. Se o texto já é apenas numérico, ele não faz nada. Se o texto filtrado for diferente, ele será gravado na @Statevariável, que inicia o loop novamente, mas desta vez o fechamento será executado sem modificar nenhuma propriedade.

Confira Usando Combinar para mais informações.

John M.
fonte
Comecei a pesquisar no Swift / SwiftUI (proveniente do mundo Windows C # e Web Typescript), e o primeiro problema que encontrei foi como filtrar a entrada do usuário para permitir apenas números. É muito comum filtrar a entrada com base em uma expressão regex. Sua solução parece ser exatamente o que eu estava procurando - muito obrigado. Eu olhei para a documentação e parece um pouco ausente na melhor das hipóteses. Você se importaria de explicar o motivo do editor 'Just'?
Jesper Kristiansen
+1 para esta resposta. No entanto, não entendo muito bem a importação do Combine para isso. O que está apenas fazendo. Obrigado
davidev 26/02
@JesperKristiansen @davidev Adicionei uma explicação do Justeditor.
John M.
Obrigado por essa boa explicação! Sua solução funciona, mas por um breve momento você pode ver o valor antigo antes de ser filtrado. Há uma maneira de prevenir isto?
Lupurus 7/04
1
E, a propósito: .onReceive é chamado sempre, quando sth. mais no modo de exibição foi alterado. Isso não é um pouco pesado demais?
Lupurus 7/04
1

Você não precisa usar Combinee onReceivetambém pode 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())
    }
}

Infelizmente, também há uma pequena oscilação, para que você também possa ver os caracteres não permitidos por um período muito curto (aos meus olhos um pouco mais curto do que o modo Combine)

Lupurus
fonte
0

Outra abordagem talvez seja criar uma View que envolva a visualização TextField e contenha dois valores: uma var particular que contenha a String inserida e um valor vinculável que contenha o equivalente Double. Cada vez que o usuário digita um caractere, ele tenta atualizar o Double.

Aqui está uma implementação 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)"})
    }
}

Você poderia usá-lo assim:

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

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

Este é um exemplo básico - você pode adicionar funcionalidades para mostrar um aviso de entrada ruim e, talvez, verificações de limites etc.

Philip Pegden
fonte