我知道这有一些公认的答案,但是在输入值时(至少对于双精度数),上述答案似乎有故障的 UX 结果。所以我决定编写自己的解决方案。它很大程度上受到了这里的答案的启发,所以我会先尝试这里的其他示例,然后再尝试这个,因为它的代码要多得多。
警告 虽然我已经做了很长时间的 iOS 开发人员,但我对 SwiftUI 还是很陌生。因此,这与专家建议相去甚远。我喜欢
反馈我的方法,但要友善。到目前为止,这一直在解决
我的新项目很好。但是,我怀疑这是否与 Apple 的格式化程序一样有效。
protocol NewFormatter {
associatedtype Value: Equatable
/// The logic that converts your value to a string presented by the `TextField`. You should omit any values
/// - Parameter object: The value you are converting to a string.
func toString(object: Value) -> String
/// Once the change is allowed and the input is final, this will convert
/// - Parameter string: The full text currently on the TextField.
func toObject(string: String) -> Value
/// Specify if the value contains a final result. If it does not, nothing will be changed yet.
/// - Parameter string: The full text currently on the TextField.
func isFinal(string: String) -> Bool
/// Specify **all** allowed inputs, **including** intermediate text that cannot be converted to your object **yet** but are necessary in the input process for a final result. It will allow this input without changing your value until a final correct value can be determined.
/// For example, `1.` is not a valid `Double`, but it does lead to `1.5`, which is. Therefore the `DoubleFormatter` would return true on `1.`.
/// Returning false will reset the input to the previous allowed value.
/// - Parameter string: The full text currently on the TextField.
func allowChange(to string: String) -> Bool
}
struct NewTextField<T: NewFormatter>: View {
let title: String
@Binding var value: T.Value
let formatter: T
@State private var previous: T.Value
@State private var previousGoodString: String? = nil
init(_ title: String, value: Binding<T.Value>, formatter: T) {
self.title = title
self._value = value
self._previous = State(initialValue: value.wrappedValue)
self.formatter = formatter
}
var body: some View {
let changedValue = Binding<String>(
get: {
if let previousGoodString = self.previousGoodString {
let previousValue = self.formatter.toObject(string: previousGoodString)
if previousValue == self.value {
return previousGoodString
}
}
let string = self.formatter.toString(object: self.value)
return string
},
set: { newString in
if self.formatter.isFinal(string: newString) {
let newValue = self.formatter.toObject(string: newString)
self.previousGoodString = newString
self.previous = newValue
self.value = newValue
} else if !self.formatter.allowChange(to: newString) {
self.value = self.previous
}
}
)
return TextField(title, text: changedValue)
}
}
然后您可以为Double 创建一个自定义格式化程序,如下所示:
/// An object that converts a double to a valid TextField value.
struct DoubleFormatter: NewFormatter {
let numberFormatter: NumberFormatter = {
let numberFormatter = NumberFormatter()
numberFormatter.allowsFloats = true
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 15
return numberFormatter
}()
/// The logic that converts your value to a string used by the TextField.
func toString(object: Double) -> String {
return numberFormatter.string(from: NSNumber(value: object)) ?? ""
}
/// The logic that converts the string to your value.
func toObject(string: String) -> Double {
return numberFormatter.number(from: string)?.doubleValue ?? 0
}
/// Specify if the value contains a final result. If it does not, nothing will be changed yet.
func isFinal(string: String) -> Bool {
return numberFormatter.number(from: string) != nil
}
/// Specify **all** allowed values, **including** intermediate text that cannot be converted to your object **yet** but are necessary in the input process for a final result.
/// For example, `1.` is not a valid `Double`, but it does lead to `1.5`, which is. It will allow this input without changing your value until a final correct value can be determined.
/// Returning false will reset the input the the previous allowed value. For example, when using the `DoubleFormatter` the input `0.1j` would result in false which would reset the value back to `0.1`.
func allowChange(to string: String) -> Bool {
let components = string.components(separatedBy: ".")
if components.count <= 2 {
// We allow an Integer or an empty value.
return components.allSatisfy({ $0 == "" || Int($0) != nil })
} else {
// If the count is > 2, we have more than one decimal
return false
}
}
}
你可以像这样使用这个新组件:
NewTextField(
"Value",
value: $bodyData.doubleData.value,
formatter: DoubleFormatter()
)
以下是我能想到的一些其他用法:
/// Just a simple passthrough formatter to use on a NewTextField
struct PassthroughFormatter: NewFormatter {
func toString(object: String) -> String {
return object
}
func toObject(string: String) -> String {
return string
}
func isFinal(string: String) -> Bool {
return true
}
func allowChange(to string: String) -> Bool {
return true
}
}
/// A formatter that converts empty strings to nil values
struct EmptyStringFormatter: NewFormatter {
func toString(object: String?) -> String {
return object ?? ""
}
func toObject(string: String) -> String? {
if !string.isEmpty {
return string
} else {
return nil
}
}
func isFinal(string: String) -> Bool {
return true
}
func allowChange(to string: String) -> Bool {
return true
}
}