如何让 SwiftUI TextField 显示数字但也接受空值

How to get SwiftUI TextField to show numbers but also accept an empty value

我正在尝试获取一个仅接受数字但也接受空值的文本字段(即它是一个可选字段)。这是文本字段的样子:

TextField("Phone Number", value: $phoneNumber, formatter: numberFormatter)
    .disableAutocorrection(true)
    .autocapitalization(.none)

我的数字格式化程序如下所示:

@State private var numberFormatter: NumberFormatter = {
    var nf = NumberFormatter()
    nf.numberStyle = .none
    nf.zeroSymbol = ""    
    return nf
}()

我尝试了两种不同的解决方案,但都存在问题。第一个解决方案是将 phoneNumber 定义为可选整数 Int? 以便 TextField 将接受空白输入:

@State var phoneNumber: Int? = nil

然而,这与 TextField 混淆,因此当我通过应用程序更改其值时,更改不会更新实际变量 phoneNumber。所以,当我去发送我的数据时,phoneNumber 仍然停留在 nil。我认为这与 numberFormatter 以及它如何不接受 nil 变量有关。

因此,我的第二个解决方案是将 phoneNumber 初始化为 Int,如下所示:

@State var phoneNumber: Int = 0

当我更改 TextField 中的文本时,此解决方案会更新 phoneNumber。但是,当我输入 0 时,它只会在 TextField 框中显示空白 space(因为我在 numberFormatter 中定义了 .zeroSymbol)。当我尝试只输入一个空白 space(即删除所有文本)然后单击 TextField 之外时,它只是恢复到之前的数字。当我在字段中输入一个非数字条目时,也会发生同样的事情,我可以接受,因为它应该只接受数字,但我仍然希望允许用户包含一个空白条目。

我正在使用 XCode 并创建一个 iOS 应用程序。感谢您的帮助。

一个可能的解决方案是通过代理绑定拦截 input/output 并执行所需的额外 validation/processing。

测试 Xcode 13.3 / iOS 15.4

这是主要部分:

TextField("Phone Number", value: Binding(
    get: { phoneNumber ?? 0},
    set: { phoneNumber = phoneNumber == [=10=] ? nil : [=10=] }
    ), formatter: numberFormatter)

Complete test module in project

我找到的另一个解决方案是将 phoneNumber 设为默认为 "" 的字符串。然后,通过 Int(phoneNumber) 尝试将其转换为 Int 来验证它。如果 Int(phoneNumber) == nil,则引发错误。否则,您现在有一个新的可选整数,可以是 nil 或提供的整数:

var intPhoneNumber: Int?
        if phoneNumber == "" {
            intPhoneNumber = nil
        } else {
            intPhoneNumber = Int(phoneNumber)
            if intPhoneNumber == nil {
                completion(.failure(.custom(errorMessage: "Please enter a valid phone number. Try again.")))
                return
            } else if intPhoneNumber! < 0 {
                completion(.failure(.custom(errorMessage: "Invalid Phone Number. Please enter a valid 10 digit phone number.")))
                return
            } else if numberOfDigits(in: intPhoneNumber!) != 10 {
                completion(.failure(.custom(errorMessage: "Invalid Phone Number. Please enter a valid 10 digit phone number.")))
                return
            }
        }