NumberField 或如何使 TextField 输入 Double、Float 或其他带点的数字

Posted

技术标签:

【中文标题】NumberField 或如何使 TextField 输入 Double、Float 或其他带点的数字【英文标题】:NumberField or how to make TextField input a Double, Float or other numbers with dot 【发布时间】:2020-01-07 02:57:58 【问题描述】:

根据this question 中的评论,我基于TextField 制作了一个自定义的 SwifUI View。它使用数字键盘,只能输入数字和点,只能有一个点(点),您可以通过View传递一个BindableDouble@State值进行输入。 但是有一个错误:当您删除“xxx.0”中的最后一个零时 - 仍然会出现零。当你删除一个点 - 零成为整数的一部分,所以它去“xxx0”

知道如何解决吗?在删除点之前的最后一个数字时,我尝试将 value 设为整数 - 但我无法捕捉到字符串中只有一个最后一个点的时刻。

这里是完整的代码:

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()
    

【问题讨论】:

【参考方案1】:

在键入时避免丢失小数点和尾随 0 的唯一方法是在调用数字字段时跟踪字符串的整数和小数位数,这意味着将数字精度值作为状态的一部分的superview。请参阅 this gist 以获取使用此技术的全功能 (Swift 5) 视图。要查看如果您不在超级视图中保持数字精度会发生什么,请比较下面预览中第一个和第二个字段的行为:第一个将按预期处理键入,第二个将删除任何尾随 .0值发生变化。

【讨论】:

【参考方案2】:

我不确定我是否真的做对了所有事情,但看起来我已经解决了这个问题。 这是代码:

import SwiftUI
import Combine
fileprivate func getTextOn(double: Double) -> String
    let rounded = double - Double(Int(double)) == 0
    var result = ""
    if double != Double.zero
        result = rounded ? String(Int(double)) : String(double)
    
    return result


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)
                    if substring.count == 0
                        if self.numericValue != 0
                            self.numericValue = 0
                        
                    else if substring.count == 1
                        var newValue: Double = 0
                        if let lastChar = substring[0].last
                            let ch = String(lastChar)
                            if ch == "."
                                newValue = Double(String(substring[0]).dropLast()) ?? 0
                            else
                                newValue = Double(String(substring[0])) ?? 0
                            
                        
                        if self.numericValue != newValue
                            self.numericValue = newValue
                        
                    else
                        let newValue =  Double(String("\(String(substring[0])).\(String(substring[1]))")) ?? 0
                        if self.numericValue != newValue
                            self.numericValue = newValue
                        

                    
                
            
        

        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: getTextOn(double: 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("123", numericValue: $numeric)
        
    


struct decimalTextField_Previews: PreviewProvider 
    static var previews: some View 
        testView()
    

但在调试中我注意到 didSet 中的代码执行了多次。不知道我的错误是什么导致了这一点。有什么建议吗?

【讨论】:

以上是关于NumberField 或如何使 TextField 输入 Double、Float 或其他带点的数字的主要内容,如果未能解决你的问题,请参考以下文章

Cx 框架:如何在 NumberField 上设置对齐:右和小数精度

在 MongoDB 中插入 MAX(numberField) + 1

Ext.form.NumberField 中的数千个分隔符

ExtJs NumberField 验证

EasyAdminBundle NumberField 是舍入纬度和经度小数

在 Sencha Touch 中设置 numberfield 的值而不触发“change”事件