Swiftui NumberField或如何使TextField输入双精度、浮点或其他带点的数字

Swiftui NumberField或如何使TextField输入双精度、浮点或其他带点的数字,swiftui,textfield,numeric,Swiftui,Textfield,Numeric,根据中的评论,我根据文本字段制作了一个自定义SwifUI视图。它使用数字键盘,只能输入数字和点,只能输入一个点,您可以通过视图传递一个可绑定的双字符串{ 设四舍五入=双精度-双精度(整数(双精度))==0 var result=“” 如果是double!=double.zero{ 结果=四舍五入?字符串(整数(双精度)):字符串(双精度) } 返回结果 } 结构域:视图{ 公共let占位符:字符串 @绑定变量numericValue:双精度 私有类DecimalTextFieldViewMode

根据中的评论,我根据
文本字段
制作了一个自定义SwifUI
视图
。它使用数字键盘,只能输入数字和点,只能输入一个点,您可以通过
视图
传递一个可绑定的
值进行输入。 但是有一个错误:当你删除“xxx.0”中最后一个零时,仍然会出现零。当你删除一个点时,零变成整数的一部分,所以它变成“xxx0”

知道怎么修吗?在删除点之前的最后一个数字时,我尝试将值设为整数,但我无法捕捉到字符串中只有最后一个点的时刻

以下是完整的代码:

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()
    }
}
导入快捷界面
进口联合收割机
结构域:视图{
公共let占位符:字符串
@绑定变量numericValue:双精度
私有类DecimalTextFieldViewModel:ObserveObject{
@已发布的var text=“”{
迪塞特{
DispatchQueue.main.async{
让substring=self.text.split(分隔符:字符(“.”),最大拆分:2)
开关子字符串.count{
案例0:
如果self.numericValue!=0{
self.numericValue=0
}
案例1:
var newValue:Double=0
如果让lastChar=substring[0]。last{
如果lastChar==字符(“.”){
newValue=Double(字符串(子字符串[0]).dropLast())??0
}否则{
newValue=Double(字符串(子字符串[0])??0
}
}
self.numericValue=newValue
违约:
self.numericValue=Double(字符串(\(字符串(子字符串[0]))。\(字符串(子字符串[1])))))?0
}
}
}
}
私有var子可取消:任何可取消!
私有变量validCharSet=CharacterSet(charactersIn:“1234567890.”)
@绑定私有var numericValue:双精度{
迪塞特{
DispatchQueue.main.async{
如果字符串(self.numericValue)!=self.text{
self.text=字符串(self.numericValue)
}
}
}
}
init(numericValue:Binding,text:String){
self.text=文本
self.\u numericValue=numericValue
subCancellable=$text.sink{val in
//检查新字符串是否包含任何无效字符
如果val.rangeOfCharacter(from:self.validCharSet.inversed)!=nil{
//清理字符串(在主线程上执行此操作以避免与当前ContentView更新周期重叠)
DispatchQueue.main.async{
self.text=字符串(self.text.unicodeScalars.filter{
self.validCharSet.contains($0)
})
}
}
}
}
脱硝{
子可取消。取消()
}
}
@观察对象私有var视图模型:DecimalTextFieldViewModel
init(占位符:String=”“,numericValue:Binding){
self.\u numericValue=numericValue
self.placeHolder=占位符
self.viewModel=DecimalTextFieldViewModel(numericValue:self.\u numericValue,text:numericValue.wrappedValue==Double.zero?“:字符串(numericValue.wrappedValue))
}
var body:一些观点{
文本字段(占位符,文本:$viewModel.text)
.键盘类型(.decimalPad)
}
}
结构测试视图:视图{
@状态变量数值:双精度=0
var body:一些观点{
返回VStack(对齐:。中心){
文本(“输入:\(字符串(数字))”)
DecimalTextField(占位符:“123”,数值:$numeric)
}
}
}
结构decimalTextField\u预览:PreviewProvider{
静态var预览:一些视图{
testView()
}
}

我不确定我是否真的把每件事都做好了,但看起来我解决了这个问题。 代码如下:

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()
    }
}
导入快捷界面
进口联合收割机
fileprivate func getTextOn(double:double)->字符串{
设四舍五入=双精度-双精度(整数(双精度))==0
var result=“”
如果是double!=double.zero{
结果=四舍五入?字符串(整数(双精度)):字符串(双精度)
}
返回结果
}
结构域:视图{
公共let占位符:字符串
@绑定变量numericValue:双精度
私有类DecimalTextFieldViewModel:ObserveObject{
@已发布的var text=“”{
迪塞特{
DispatchQueue.main.async{
让substring=self.text.split(分隔符:字符(“.”),最大拆分:2)
如果substring.count==0{
如果self.numericValue!=0{
self.numericValue=0
}
}如果substring.count==1,则为else{
var newValue:Double=0
如果让lastChar=substring[0]。last{
设ch=String(lastChar)
如果ch==”{
newValue=Double(字符串(子字符串[0]).dropLast())??0
}否则{
newValue=Double(字符串(子字符串[0])??0
}
}
如果self.numericValue!=newValue{
self.numericValue=newValue
}
}否则{
设newValue=Double(字符串(\(字符串(子字符串[0]))。\(St