即时编辑 SwiftUI TextField

Editing a SwiftUI TextField on the fly

我希望能够在用户逐字符键入时编辑文本字段。我希望能够获取一个数字字段并在用户输入时输入:

  1. 过滤掉非数字字符并return仅返回已输入的有效字符。
  2. 格式化数字,以便在用户输入时得到相反的数字。即 120.36 将显示为: 0.01 0.12 1.20 12.03 120.36

如果用户输入了无效字符,则 return 不包含无效字符的字符串。 我对如何捕获 TextField、编辑它以及 return 它的格式如我所展示的感到困惑。

我已经走到这一步了:(不远了:-) 我已经在 Whosebug 上尝试了很多解决方案,但 none 似乎让我到达了我想去的地方.. onEditChange 变化时 等等

构造 SwiftUIView:视图 {

@State var amount: String = "0.00"
var body: some View {
    
    TextField(
        "Enter amount here ",
        text: $amount
    )

}

}

我怀疑我的要求太宽泛了,可能有点要求为我做程序的味道。事实并非如此,但也许我可以再缩小范围。
无论如何,我能够达到我想要的结果。我认为这是一个巨大的 KLUDGE,但这是我所能想到的。

首先是我的 SwiftUI 视图:

struct SwiftUIView: View {

    @State var someText: String = "0.00"
    @State var oldText: String = ""
    @State var amount: Double = 0.00

    var body: some View {
    VStack {
        TextField("Enter some text here", text: $someText,
                  onCommit: {
                    print("someText = \(someText)")
                    self.amount = Double(someText)!
                    }
        )
            .onChange(of: someText, perform: { value in
                    if someText != oldText {
                        someText = editInputNumber(textIn: someText)
                        oldText = someText
                    }
                }
            
            )
        
            Text("The amount entered was \(self.amount)")

            }
    }
}

使用 onChange 我 运行 解决了以编程方式更改绑定字段第二次触发 onChange 的问题。这是 Kludge 的最大部分,我必须在其中测试对 onChange 的第二次调用。

然后是我创建的函数来响应用户的输入。 此函数从视图中调用,它接收字符串并首先调用一个函数将其过滤为仅数字。消除意外输入的任何非数字

它获取结果并将其提交给格式化函数并取回格式化的字符串。

func editInputNumber(textIn: String) -> String
{
        var fixedText = ""
        var charactersWork = [Character]()
        charactersWork = getNumericInputString(textIn: textIn)
        fixedText = formatDecimalNumericString(charactersWork: 
        charactersWork)
        return fixedText
}

将输入字符串过滤为仅数字的功能。它也过滤掉先前条目中的任何前导零。

func getNumericInputString(textIn: String) -> [Character]
{
        var leadingZero = true
        let charactersIn = Array(textIn)
        var charactersOut = [Character]()
        for i in 0..<charactersIn.count {
            if charactersIn[i].isNumber {
                if let number = Int(String(charactersIn[i]))
                {
                    if number > 0
                    {
                        leadingZero = false
                    }
                }
                if !leadingZero {
                    charactersOut.append(charactersIn[i])
                }
            }
        }

    return charactersOut
}

在将字符串过滤为仅包含数字的字符数组后,此函数会对其进行格式化以便输出回视图。如果它是输入的前两个数字,函数会在值上设置前导零。

func formatDecimalNumericString(charactersWork: [Character]) - 
   > String
{
    let number: String = "000"
    var charactersOut = [Character]()
    if charactersWork.count < 3
    {
        charactersOut = Array(number)
    }

    if charactersWork.count > 0
    {
        if charactersWork.count == 1
        {
            charactersOut[2] = charactersWork[0]
        }
        if charactersWork.count == 2
        { 
            charactersOut[1] = charactersWork[0]
            charactersOut[2] = charactersWork[1]
        }
        if charactersWork.count > 2
        {
            for i in 0..<charactersWork.count
            {
                charactersOut.append(charactersWork[i])
            }
        }
        charactersOut.insert(".", at: (charactersOut.count - 2))
    }
    return String(charactersOut)
}

我运行进入同样的问题!使用已接受的答案作为灵感(感谢您的发帖!)我能够想出一个更少 kludgy 的解决方案。

import SwiftUI

struct Example: View {
    
    @Binding var valueText: String
    
    var body: some View {
        TextField("Enter Value", text: $valueText)
            .onChange(of: valueText, perform: { newValue in
                valueText = editOnTheFly(input: newValue, leadingZeros: 0, decimalPlaces: 2)
            })
    }
    
    func editOnTheFly(input: String, leadingZeros: Int, decimalPlaces: Int) -> String {
        // Strip all non-numeric characters from the String
        var result = input.replacingOccurrences(of: "[^0123456789]", with: "", options: .regularExpression)
        
        // If the String can't be cast as a Double return ""
        guard let resultAsDouble = Double(result) else {
            return ""
        }
        
        let formatter = NumberFormatter()
        formatter.minimumIntegerDigits = leadingZeros
        formatter.minimumFractionDigits = decimalPlaces
        formatter.maximumFractionDigits = decimalPlaces

        // If `2` decimalPlaces past in, the divider would be 100.0
        let dividerDecimal = pow(10.0, decimalPlaces)
        let dividerDouble = NSDecimalNumber(decimal: dividerDecimal).doubleValue
        
        // Turns an input of `42` into `0.42`
        result = formatter.string(from: NSNumber(value: resultAsDouble/dividerDouble)) ?? ""
        
        return result
    }
}