Hello!
Could somebody explain me this code:
TextField("Number of Pages", text: $bookPages)
// Filter the input for only numbers
.keyboardType(.numberPad)
.onReceive(Just(bookPages)) { newValue in
let filtered = newValue.filter { "0123456789".contains($0) }
if filtered != newValue {
self.bookPages = filtered
}
}
Thanks
@takipeti90
Hi Peter,
The code essentially only accepts numeric input from the keyboard.
You would think that setting the keyboard type to .numberPad
would be all that you need to do but an iPad does not have an exclusively numeric keypad.
To prevent any other characters from being entered, the additional code in .onRecieve
has been included.
.onReceive
monitors a Published item and in this case bookPages
is wrapped with Just
which is a Combine publisher which emits (broadcasts) the contents of a property.
newValue
is the current contents of the TextField as entered by the user which may include non numeric characters.
The line of code: let filtered = newValue.filter { "0123456789".contains($0) }
is doing quite a bit of work.
newValue.filter
iterates through the contents of newValue
.
The trailing closure: { "0123456789".contains($0)
is saying, "for each iterated value (denoted by $0), does that character exist in the string â0123456789â. If it does then add it to filtered
".
The test: if filtered != newValue {
is saying if the filtered
characters does not equal the characters the user entered then set bookPages
to be the filtered
value (cleaned).
Hope that helps.
2 Likes
Thank you for the detailed explanation.
The only thing what is not totally clear for me is that, the .onReceive will run after every single character (and do the filtering on that character) or only after I entered all number and clicked somewhere else on the screen.
Why does the code need this Just()? What does it do exactly? Could the code work without it?
Thanks
The property bookPages
is a State object so what Just
(which is a structure from the Combine framework) does is turn it into a Published property so that .onReceive
will fire. As I said in my previous reply, .onReceive
requires a Published property to react or take action.
Each time you type a character Just detects a change to the property and âemitsâ. This might all be a bit hard to get your head around but so is a lot of things related to Combine
.
Run the following code in a test SwiftUI project to observe the output where the Text view says "Contents of bookPages: " You will see that every character is checked and anything non numeric is rejected. I deliberately added a delay so that you can see that the non numeric characters typed in are initially visible but the filter process removes them. Without that delay you donât see that you have typed a non numeric character because it is removed as fast as you type it.
import Combine
import SwiftUI
struct ContentView: View {
@State private var bookPages: String = ""
var body: some View {
VStack(spacing: 40) {
TextField("Number of Pages", text: $bookPages)
// Filter the input for only numbers
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
.onReceive(Just(bookPages)) { newValue in
let filtered = newValue.filter { "0123456789".contains($0) }
if filtered != newValue {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.bookPages = filtered
}
}
}
.padding(.horizontal, 20)
Text("Contents of bookPages: \(bookPages)")
}
}
}
Exactly the same thing could have been achieved by using .onChange()
rather than .onReceive()
but you would not get some exposure to using Combine
which is always worth while.
import SwiftUI
struct ContentView: View {
@State private var bookPages: String = ""
var body: some View {
VStack(spacing: 40) {
TextField("Number of Pages", text: $bookPages)
// Filter the input for only numbers
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
.onChange(of: bookPages, perform: { newValue in
let filtered = newValue.filter { "0123456789".contains($0) }
if filtered != newValue {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.bookPages = filtered
}
}
})
.padding(.horizontal, 20)
Text("Contents of bookPages: \(bookPages)")
}
}
}
Thank you, now I understand more or less
Do you have tutorial or video or something about this Combine framework?
Chris Ching doesnât have any Combine videos but there are plenty of resources if you search online. I bought a book âPractical Combineâ from Donny Wals. Havenât even got around to reading it as yet and I bought it months ago. Life is like that sometimes.