Swift UI How to create TextField that accepts numbers only

 ̄綄美尐妖づ 提交于 2020-05-11 04:37:33

问题


Im new to swiftUI and iOs, im trying to create an input field that will only accept numbers

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

the TextField also allows alphabetic characters, how do I limit user to input only numbers?


回答1:


You can set the type of keyboard on the TextField which will limit what people can type on.

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

Apple's documentation can be found here, and you can see a list of all supported keyboard types here.

Note: this will not work on an iPad as there no number pad for the iPad. For a better solution checkout John M's solution below https://stackoverflow.com/a/58736068/5508175




回答2:


Although showing a number pad is a good first step, it does not actually prevent bad data from being entered:

  1. The user can paste non-numeric text into the textfield
  2. iPad users will still get a full keyboard
  3. Anyone with a Bluetooth keyboard attached can type anything

What you really want to do is sanitize the input, like this:

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

Whenever numOfPeople changes, the non-numeric values are filtered out, and the filtered value is compared to see if numOfPeople should be updated a second time, overwriting the bad input with the filtered input.

Note that the Just publisher requires that you import Combine.

EDIT:

To explain the Just publisher, consider the following conceptual outline of what occurs when you change the value in the TextField:

  1. Because TextField takes a Binding to a String, when the contents of the field are changed, it also writes that change back to the @State variable.
  2. When a variable marked @State changes, SwiftUI recomputes the body property of the view.
  3. During the body computation, a Just publisher is created. Combine has a lot of different publishers to emit values over time, but the Just publisher takes "just" a single value (the new value of numberOfPeople) and emits it when asked.
  4. The onReceive method makes a View a subscriber to a publisher, in this case, the Just publisher we just created. Once subscribed, it immediately asks for any available values from the publisher, of which there is only one, the new value of numberOfPeople.
  5. When the onReceive subscriber receives a value, it executes the specified closure. Our closure can end one of two ways. If the text is already numeric only, then it does nothing. If the filtered text is different, it is written to the @State variable, which begins the loop again, but this time the closure will execute without modifying any properties.

Check out Using Combine for more info.




回答3:


You don't need to use Combine and onReceive, you can also use this code:

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

Unfortunately there is also a small flickering, so you also can see the non-allowed characters for a very short time (in my eyes a little bit shorter as the way with Combine)




回答4:


Another approach perhaps is to create a View that wraps the TextField view, and holds two values: a private var holding the entered String, and a bindable value that holds the Double equivalent. Each time the user types a character it try's to update the Double.

Here's a basic implementation:

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

You could use it like this:

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

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

This is a bare-bones example - you might want to add functionality to show a warning for poor input, and perhaps bounds checks etc...



来源:https://stackoverflow.com/questions/58733003/swift-ui-how-to-create-textfield-that-accepts-numbers-only

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!