Although showing a number pad is a good first step, it does not actually prevent bad data from being entered:
- The user can paste the non-numeric text into the
TextField - iPad users will still get a full keyboard
- 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:
- Because
TextFieldtakes aBindingto aStringwhen the contents of the field are changed, it also writes that change back to the@Statevariable. - When a variable marked
@Statechanges, SwiftUI recomputes thebodyproperty of the view. - During the
bodycomputation, aJustpublisher is created. Combine has a lot of different publishers to emit values over time, but theJustpublisher takes “just” a single value (the new value ofnumberOfPeople) and emits it when asked. - The
onReceivemethod makes aViewa subscriber to a publisher, in this case, theJustpublisher we just created. Once subscribed, it immediately asks for any available values from the publisher, of which there is only one, the new value ofnumberOfPeople. - When the
onReceivesubscriber receives a value, it executes the specified closure. Our closure can end in 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@Statevariable, which begins the loop again, but this time the closure will execute without modifying any properties.
Check out Using Combine for more info.