NumberField 或如何使 TextField 输入带点的 Double、Float 或其他数字 [英] NumberField or how to make TextField input a Double, Float or other numbers with dot

查看:66
本文介绍了NumberField 或如何使 TextField 输入带点的 Double、Float 或其他数字的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据

知道如何修复它吗?在删除点之前的最后一个数字时,我试图将值设为整数 - 但我无法捕捉到字符串中只有最后一个点的时刻.

这是完整的代码:

导入 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆