如何创建只接受数字和单个点的SwiftUI TextField? [英] How to create SwiftUI TextField that accepts only numbers and a single dot?
问题描述
如何创建一个swiftui文本字段,允许用户仅输入数字和单个点?换句话说,它会在用户输入时逐位检查数字,如果输入是数字或点,并且文本字段没有其他点,则接受该数字,否则将忽略该数字条目.不能使用步进器.
How to create a swiftui textfield that allows the user to only input numbers and a single dot? In other words, it checks digit by digit as the user inputs, if the input is a number or a dot and the textfield doesn't have another dot the digit is accepted, otherwise the digit entry is ignored. Using a stepper isn't an option.
推荐答案
SwiftUI不允许您为 TextField
指定一组允许的字符.实际上,这与UI本身无关,而与您如何管理背后的模型有关.在这种情况下,模型是 TextField
后面的文本.因此,您需要更改视图模型.
SwiftUI doesn't let you specify a set of allowed characters for a TextField
. Actually, it's not something related to the UI itself, but to how you manage the model behind. In this case the model is the text behind the TextField
. So, you need to change your view model.
如果在 @Published
属性上使用 $
符号,则可以访问 @Published
属性本身.然后,您可以将自己的订阅者附加到发布者,并执行所需的任何检查.在这种情况下,我使用了 sink
函数将基于闭包的订阅者附加到发布者:
If you use the $
sign on a @Published
property you can get access to the Publisher
behind the @Published
property itself. Then you can attach your own subscriber to the publisher and perform any check you want. In this case I used the sink
function to attach a closure based subscriber to the publisher:
/// Attaches a subscriber with closure-based behavior.
///
/// This method creates the subscriber and immediately requests an unlimited number of values, prior to returning the subscriber.
/// - parameter receiveValue: The closure to execute on receipt of a value.
/// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream.
public func sink(receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable
实现:
import SwiftUI
import Combine
class ViewModel: ObservableObject {
@Published var text = ""
private var subCancellable: AnyCancellable!
private var validCharSet = CharacterSet(charactersIn: "1234567890.")
init() {
subCancellable = $text.sink { val in
//check if the new string contains any invalid characters
if val.rangeOfCharacter(from: self.validCharSet.inverted) != nil {
//clean the string (do this on the main thread to avoid overlapping with the current ContentView update cycle)
DispatchQueue.main.async {
self.text = String(self.text.unicodeScalars.filter {
self.validCharSet.contains($0)
})
}
}
}
}
deinit {
subCancellable.cancel()
}
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
TextField("Type something...", text: $viewModel.text)
}
}
重要说明:
-
$ text
(@Published
属性上的$
符号)为我们提供了Published< String>类型的对象.
即发布者 -
$ viewModel.text
(@ObservableObject
上的$
符号)为我们提供了Binding< String>
$text
($
sign on a@Published
property) gives us an object of typePublished<String>.Publisher
i.e. a publisher$viewModel.text
($
sign on an@ObservableObject
) gives us an object of typeBinding<String>
那是完全不同的两件事.
That are two completely different things.
编辑:如果需要,您甚至可以使用此行为创建自己的自定义 TextField
.假设您要创建一个 DecimalTextField
视图:
EDIT: If you want you can even create you own custom TextField
with this behaviour. Let's say you want to create a DecimalTextField
view:
import SwiftUI
import Combine
struct DecimalTextField: View {
private class DecimalTextFieldViewModel: ObservableObject {
@Published var text = ""
private var subCancellable: AnyCancellable!
private var validCharSet = CharacterSet(charactersIn: "1234567890.")
init() {
subCancellable = $text.sink { val in
//check if the new string contains any invalid characters
if val.rangeOfCharacter(from: self.validCharSet.inverted) != nil {
//clean the string (do this on the main thread to avoid overlapping with the current ContentView update cycle)
DispatchQueue.main.async {
self.text = String(self.text.unicodeScalars.filter {
self.validCharSet.contains($0)
})
}
}
}
}
deinit {
subCancellable.cancel()
}
}
@ObservedObject private var viewModel = DecimalTextFieldViewModel()
var body: some View {
TextField("Type something...", text: $viewModel.text)
}
}
struct ContentView: View {
var body: some View {
DecimalTextField()
}
}
通过这种方式,您可以在编写时使用自定义文本字段:
This way you can use your custom text field just writing:
DecimalTextField()
,您可以在任何需要的地方使用它.
and you can use it wherever you want.
这篇关于如何创建只接受数字和单个点的SwiftUI TextField?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!