NumberField 或如何使 TextField 输入带点的 Double、Float 或其他数字 [英] NumberField or how to make TextField input a Double, Float or other numbers with dot
问题描述
根据
知道如何修复它吗?在删除点之前的最后一个数字时,我试图将值设为整数 - 但我无法捕捉到字符串中只有最后一个点的时刻.
这是完整的代码:
导入 SwiftUI进口结合struct DecimalTextField:查看{public let placeHolder: 字符串@Binding var numericValue: Double私有类 DecimalTextFieldViewModel:ObservableObject {@Published var text = ""{已设置{DispatchQueue.main.async {let substring = self.text.split(separator: Character("."), maxSplits: 2)切换子串.计数{案例0:如果 self.numericValue != 0{self.numericValue = 0}情况1 :var newValue: 双精度 = 0如果让 lastChar = substring[0].last{if lastChar == Character("."){newValue = Double(String(substring[0]).dropLast()) ??0}别的{newValue = Double(String(substring[0])) ??0}}self.numericValue = newValue默认:self.numericValue = Double(String("\(String(substring[0])).\(String(substring[1]))")) ??0}}}}私有变量 subCancellable: AnyCancellable!private var validCharSet = CharacterSet(charactersIn: "1234567890.")@Binding 私有 var numericValue: Double{已设置{DispatchQueue.main.async {if String(self.numericValue) != self.text {self.text = String(self.numericValue)}}}}init(numericValue: Binding, text: String) {self.text = 文本self._numericValue = numericValuesubCancellable = $text.sink { val in//检查新字符串是否包含任何无效字符如果 val.rangeOfCharacter(from: self.validCharSet.inverted) != nil {//清理字符串(在主线程上执行此操作以避免与当前的ContentView更新周期重叠)DispatchQueue.main.async {self.text = String(self.text.unicodeScalars.filter {self.validCharSet.contains($0)})}}}}去初始化{subCancellable.cancel()}}@ObservedObject 私有变量 viewModel:DecimalTextFieldViewModelinit(placeHolder: String = "", numericValue: Binding){self._numericValue = numericValueself.placeHolder = placeHolderself.viewModel = DecimalTextFieldViewModel(numericValue: self._numericValue, text: numericValue.wrappedValue == Double.zero ?"" : String(numericValue.wrappedValue))}var主体:一些视图{文本字段(placeHolder,文本:$viewModel.text).keyboardType(.decimalPad)}}结构测试视图:查看{@State var 数字:Double = 0var body: 一些视图{返回 VStack(对齐:.center){文本(输入:\(字符串(数字))")DecimalTextField(placeHolder: "123", numericValue: $numeric)}}}struct decimalTextField_Previews: PreviewProvider {静态 var 预览:一些视图 {测试视图()}}
在键入时避免丢失小数点和尾随 0 的唯一方法是在调用数字时跟踪字符串的整数和小数位数字段,这意味着将数字精度值作为父视图状态的一部分.请参阅
According to comment in this question I made a custom SwifUI View
based on a TextField
. It use numeric keypad, you can't input there nothing but numbers and point, there can be only one point (dot), and you can pass a Bindable
Double
@State
value through the View
for input.
But there is a bug: when you deleting a last zero in "xxx.0" - zero still comes out. When you deleting a dot - zero becomes a part of integer, so it goes to "xxx0"
Any idea how to fix it? I tried to make value an integer when deleting last number before dot - but I can't catch the moment when there is only one last dot in a string.
here's full code:
import SwiftUI
import Combine
struct DecimalTextField: View {
public let placeHolder: String
@Binding var numericValue: Double
private class DecimalTextFieldViewModel: ObservableObject {
@Published var text = ""{
didSet{
DispatchQueue.main.async {
let substring = self.text.split(separator: Character("."), maxSplits: 2)
switch substring.count{
case 0:
if self.numericValue != 0{
self.numericValue = 0
}
case 1 :
var newValue: Double = 0
if let lastChar = substring[0].last{
if lastChar == Character("."){
newValue = Double(String(substring[0]).dropLast()) ?? 0
}else{
newValue = Double(String(substring[0])) ?? 0
}
}
self.numericValue = newValue
default:
self.numericValue = Double(String("\(String(substring[0])).\(String(substring[1]))")) ?? 0
}
}
}
}
private var subCancellable: AnyCancellable!
private var validCharSet = CharacterSet(charactersIn: "1234567890.")
@Binding private var numericValue: Double{
didSet{
DispatchQueue.main.async {
if String(self.numericValue) != self.text {
self.text = String(self.numericValue)
}
}
}
}
init(numericValue: Binding<Double>, text: String) {
self.text = text
self._numericValue = numericValue
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
init(placeHolder: String = "", numericValue: Binding<Double>){
self._numericValue = numericValue
self.placeHolder = placeHolder
self.viewModel = DecimalTextFieldViewModel(numericValue: self._numericValue, text: numericValue.wrappedValue == Double.zero ? "" : String(numericValue.wrappedValue))
}
var body: some View {
TextField(placeHolder, text: $viewModel.text)
.keyboardType(.decimalPad)
}
}
struct testView: View{
@State var numeric: Double = 0
var body: some View{
return VStack(alignment: .center){
Text("input: \(String(numeric))")
DecimalTextField(placeHolder: "123", numericValue: $numeric)
}
}
}
struct decimalTextField_Previews: PreviewProvider {
static var previews: some View {
testView()
}
}
The only way to avoid loss of the decimal point and trailing 0's while typing is to keep track of the integral and fractional digits of the string across invocations of the numeric field, which means keeping the digit precision values as part of the state of the superview. See this gist for a fully functional (Swift 5) view that uses this technique. To see what happens if you don't keep the digit precision in the superview, compare the behavior of the first and second fields in the preview below: the first will handle typing as expected, the second will remove any trailing .0 as soon as the value changes.
这篇关于NumberField 或如何使 TextField 输入带点的 Double、Float 或其他数字的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!