45

我是 SwiftUI 和 iOS 的新手,我正在尝试创建一个只接受数字的输入字段。

 TextField("Total number of people", text: $numOfPeople)

目前TextField允许使用字母字符,我该如何使用户只能输入数字?

4

19 回答 19

123

虽然显示数字键盘是一个很好的第一步,但它实际上并不能防止输入错误数据:

  1. 用户可以将非数字文本粘贴到文本字段中
  2. iPad 用户仍将获得全键盘
  3. 任何连接了蓝牙键盘的人都可以输入任何内容

您真正想要做的是清理输入,如下所示:

import SwiftUI
import Combine

struct StackOverflowTests: View {
    @State private var numOfPeople = "0"

    var body: some View {
        TextField("Total number of people", text: $numOfPeople)
            .keyboardType(.numberPad)
            .onReceive(Just(numOfPeople)) { newValue in
                let filtered = newValue.filter { "0123456789".contains($0) }
                if filtered != newValue {
                    self.numOfPeople = filtered
                }
        }
    }
}

每当numOfPeople发生变化时,非数字值都会被过滤掉,并比较过滤后的值以查看是否numOfPeople应该再次更新,用过滤后的输入覆盖错误的输入。

请注意,Just发布者要求您import Combine.

编辑:

为了解释Just发布者,请考虑以下关于更改 中的值时发生的情况的概念大纲TextField

  1. 因为TextField需要 aBinding到 a String,所以当字段的内容发生更改时,它也会将该更改写回@State变量。
  2. 当标记的变量@State发生变化时,SwiftUI 会重新计算body视图的属性。
  3. body计算过程中,Just会创建一个发布者。Combine 有很多不同的发布者随着时间的推移发出值,但是Just发布者“只”接受一个值( 的新值numberOfPeople)并在被询问时发出它。
  4. onReceive方法使View订阅者成为发布者,在本例中,是Just我们刚刚创建的发布者。订阅后,它会立即向发布者请求任何可用值,其中只有一个,即 的新值numberOfPeople
  5. onReceive订阅者收到一个值时,它会执行指定的闭包。我们的关闭可以以两种方式之一结束。如果文本已经只是数字,那么它什么也不做。如果过滤后的文本不同,则将其写入@State变量,该变量再次开始循环,但这一次闭包将在不修改任何属性的情况下执行。

查看使用组合了解更多信息。

于 2019-11-06T18:02:38.070 回答
46

tl;博士

查看John M解决方案以获得更好的方法。


一种方法是您可以设置键盘类型,TextField这将限制人们可以输入的内容。

TextField("Total number of people", text: $numOfPeople)
    .keyboardType(.numberPad)

可以在此处找到 Apple 的文档,您可以在此处查看所有支持的键盘类型的列表

然而,这种方法只是第一步,作为唯一的解决方案并不理想:

  1. iPad 没有 numberPad,因此此方法不适用于 iPad。
  2. 如果用户使用的是硬件键盘,那么此方法将不起作用。
  3. 它不检查用户输入的内容。用户可以将非数字值复制/粘贴到 TextField 中。

您应该清理输入的数据并确保它是纯数字的。

对于执行该检查的解决方案,请查看以下John M的解决方案。他很好地解释了如何清理数据及其工作原理。

于 2019-11-06T15:34:47.960 回答
20

在我看来,更容易的是使用自定义绑定并将任何字符串直接转换为数值。这样,您还可以将 State 变量作为数字而不是string,这是 IMO 的一大优势。

以下是所需的所有代码。请注意,在无法转换字符串的情况下使用默认值(在这种情况下为零)。

@State private var myValue: Int
// ...
TextField("number", text: Binding(
    get: { String(myValue) }, 
    set: { myValue = Int($0) ?? 0 }
))
于 2020-12-20T22:55:16.973 回答
13

ViewModifier@John M. 的答案版本

import Combine
import SwiftUI

public struct NumberOnlyViewModifier: ViewModifier {

    @Binding var text: String

    public init(text: Binding<String>) {
        self._text = text
    }

    public func body(content: Content) -> some View {
        content
            .keyboardType(.numberPad)
            .onReceive(Just(text)) { newValue in
                let filtered = newValue.filter { "0123456789".contains($0) }
                if filtered != newValue {
                    self.text = filtered
                }
            }
    }
}

于 2020-05-24T10:35:32.583 回答
11

受到约翰 M.回答的极大启发,我稍微修改了一下。

对我来说,在 Xcode 12 和 iOS 14 上,我注意到输入字母确实显示在 中TextField,尽管我不希望它们出现。我希望忽略字母,允许使用数字。

这是我所做的:

@State private var goalValue = ""

var body: some View {
    TextField("12345", text: self.$goalValue)
        .keyboardType(.numberPad)
        .onReceive(Just(self.goalValue), perform: self.numericValidator)
}

func numericValidator(newValue: String) {
    if newValue.range(of: "^\\d+$", options: .regularExpression) != nil {
        self.goalValue = newValue
    } else if !self.goalValue.isEmpty {
        self.goalValue = String(newValue.prefix(self.goalValue.count - 1))
    }
}

这里的关键是else if; 这将基础变量的值设置为除了最近的字符之外的所有内容。

另外值得注意的是,如果您想允许十进制数字而不是仅限于整数,您可以将正则表达式字符串更改为"^[\d]+\.?[\d]+$",您必须将其转义为"^[\\d]+\\.?[\\d]+$".

于 2020-09-24T14:04:08.493 回答
9

可以将 NumberFormatter 交给 TextField 并让它为您处理转换:

struct MyView: View {
    @State private var value = 42 // Note, integer value
    var body: some View {
        // NumberFormatter will parse the text and cast to integer
        TextField("title", value: $value, formatter: NumberFormatter())
    }
}

请注意,格式化程序将在用户完成编辑后应用。如果用户输入了 NumberFormatter 无法格式化的文本,则该值不会改变。因此,这可能会或可能不会涵盖您的问题“仅接受数字的文本字段”。

于 2021-01-15T15:29:14.090 回答
8

大多数答案都有一些明显的缺点。菲利普的回答是迄今为止最好的恕我直言。大多数其他答案在键入时不会过滤掉非数字字符。相反,您必须等到用户完成编辑后,然后他们更新文本以删除非数字字符。然后下一个常见问题是当输入语言不使用 ASCII 0-9 字符作为数字时,它们不处理数字。

我想出了一个类似于 Philip 的解决方案,但更适合生产。数字文本 SPM 包

首先,您需要一种从字符串中正确过滤非数字字符的方法,该方法适用于 unicode。

public extension String {
    func numericValue(allowDecimalSeparator: Bool) -> String {
        var hasFoundDecimal = false
        return self.filter {
            if $0.isWholeNumber {
                return true
            } else if allowDecimalSeparator && String($0) == (Locale.current.decimalSeparator ?? ".") {
                defer { hasFoundDecimal = true }
                return !hasFoundDecimal
            }
            return false
        }
    }
}

然后将文本字段包装在新视图中。我希望我能把这一切作为修饰符。虽然我可以将字符串过滤为一个,但您失去了文本字段绑定数字值的能力。

public struct NumericTextField: View {

    @Binding private var number: NSNumber?
    @State private var string: String
    private let isDecimalAllowed: Bool
    private let formatter: NumberFormatter = NumberFormatter()

    private let title: LocalizedStringKey
    private let onEditingChanged: (Bool) -> Void
    private let onCommit: () -> Void

    public init(_ titleKey: LocalizedStringKey, number: Binding<NSNumber?>, isDecimalAllowed: Bool, onEditingChanged: @escaping (Bool) -> Void = { _ in }, onCommit: @escaping () -> Void = {}) {
        formatter.numberStyle = .decimal
        _number = number
        if let number = number.wrappedValue, let string = formatter.string(from: number) {
            _string = State(initialValue: string)
        } else {
            _string = State(initialValue: "")
        }
        self.isDecimalAllowed = isDecimalAllowed
        title = titleKey
        self.onEditingChanged = onEditingChanged
        self.onCommit = onCommit
    }

    public var body: some View {
        return TextField(title, text: $string, onEditingChanged: onEditingChanged, onCommit: onCommit)
            .onChange(of: string, perform: numberChanged(newValue:))
            .modifier(KeyboardModifier(isDecimalAllowed: isDecimalAllowed))
    }

    private func numberChanged(newValue: String) {
        let numeric = newValue.numericValue(allowDecimalSeparator: isDecimalAllowed)
        if newValue != numeric {
            string = numeric
        }
        number = formatter.number(from: string)
    }
}

您并不严格需要此修饰符,但似乎您几乎总是需要它。

private struct KeyboardModifier: ViewModifier {
    let isDecimalAllowed: Bool

    func body(content: Content) -> some View {
        #if os(iOS)
            return content
                .keyboardType(isDecimalAllowed ? .decimalPad : .numberPad)
        #else
            return content
        #endif
    }
}
于 2020-07-12T23:13:31.133 回答
6

另一种方法可能是创建一个包装 TextField 视图的视图,并保存两个值:一个私有 var 保存输入的字符串,一个可绑定值保存 Double 等价物。每次用户键入一个字符时,它都会尝试更新 Double。

这是一个基本的实现:

struct NumberEntryField : View {
    @State private var enteredValue : String = ""
    @Binding var value : Double

    var body: some View {        
        return TextField("", text: $enteredValue)
            .onReceive(Just(enteredValue)) { typedValue in
                if let newValue = Double(typedValue) {
                    self.value = newValue
                }
        }.onAppear(perform:{self.enteredValue = "\(self.value)"})
    }
}

你可以像这样使用它:

struct MyView : View {
    @State var doubleValue : Double = 1.56

    var body: some View {        
        return HStack {
             Text("Numeric field:")
             NumberEntryField(value: self.$doubleValue)   
            }
      }
}

这是一个简单的示例 - 您可能想要添加功能以显示输入不良的警告,也许还有边界检查等......

于 2020-04-15T18:42:15.973 回答
4

您还可以使用简单的格式化程序

struct AView: View {
    @State var numberValue:Float
    var body: some View {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return TextField("number", value: $numberValue, formatter: NumberFormatter())
}

用户仍然可以尝试输入一些文本,如下所示:

演示

但是格式化程序强制使用一个数字。

于 2021-07-15T14:05:18.813 回答
4

您不需要使用Combineand onReceive,您也可以使用以下代码:

class Model: ObservableObject {
    @Published var text : String = ""
}

struct ContentView: View {

    @EnvironmentObject var model: Model

    var body: some View {
        TextField("enter a number ...", text: Binding(get: { self.model.text },
                                                      set: { self.model.text = $0.filter { "0123456789".contains($0) } }))
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(Model())
    }
}

不幸的是还有一个小的闪烁,所以你也可以在很短的时间内看到不允许的字符(在我眼里有点短Combine

于 2020-04-14T12:51:49.023 回答
4

第一次发帖,如有错误请见谅。在我目前的项目中,我一直在努力解决这个问题。许多答案都很好,但仅适用于特定问题,就我而言,没有一个满足所有要求。

具体来说,我需要:

  1. 多个文本字段中的纯数字用户输入,包括负数。
  2. 将该输入绑定到来自 ObservableObject 类的 Double 类型的 var,以用于多个计算。

John M 的解决方案很棒,但它绑定到一个字符串 @State 私有变量。

jamone 的回答,他的 NumericText 解决方案在很多方面都很棒,我在我的项目的 iOS14 版本中实现了它。不幸的是,它不允许输入负数。

我想出的解决方案主要基于 John M 的回答,但结合了我从 jamone 的 NumericText 代码中学到的 onEditingChanged 的​​使用。这允许我根据 John M 的解决方案清理用户输入文本,然后(使用 onEditingChanged 调用的闭包)将该字符串绑定到 Observable Object Double。

所以我在下面的内容中确实没有什么新东西,对于更有经验的开发人员来说可能是显而易见的。但是在我所有的搜索中,我从未偶然发现过这个解决方案,所以我把它贴在这里以防它帮助其他人。

import Foundation
import Combine

class YourData: ObservableObject {
    @Published var number = 0
}

func convertString(string: String) -> Double {
    guard let doubleString = Double(string) else { return 0 }
    return doubleString
}

struct ContentView: View {

    @State private var input = ""
    @EnvironmentObject var data: YourData

    var body: some View { 
        
        TextField("Enter string", text: $input, onEditingChanged: { 
            _ in self.data.number = convertString(string: self.input) })
            .keyboardType(.numbersAndPunctuation)

            .onReceive(Just(input)) { cleanNum in
                let filtered = cleanNum.filter {"0123456789.-".contains($0)}
                if filtered != cleanNum {
                    self.input = filtered
                }
            }
        }
}
于 2020-07-27T17:58:42.153 回答
1

我提出了一个基于@John M. 和@hstdt 的版本来处理:

  • 从绑定值开始

  • 负数

  • 小数点分隔符(如果超过一个,则截断字符串)

    struct NumberField : View {
    
      @Binding var value : Double
      @State private var enteredValue = "#START#"
    
      var body: some View {
          return TextField("", text: $enteredValue)
              .onReceive(Just(enteredValue)) { typedValue in
                  var typedValue_ = typedValue == "#START#" ? String(self.value) : typedValue
                  if typedValue != "" {
                      let negative = typedValue_.hasPrefix("-") ? "-" : ""
                      typedValue_ = typedValue_.filter { "0123456789.".contains($0) }
                      let parts = typedValue_.split(separator: ".")
                      let formatedValue = parts.count == 1 ? negative + String(parts[0]) : negative + String(parts[0]) + "." + String(parts[1])
                      self.enteredValue = formatedValue
                  }
                  let newValue = Double(self.enteredValue) ?? 0.0
                  self.value = newValue
    
          }
          .onAppear(perform:{
              self.enteredValue = "\(self.value)"
          })
      }
    }
    
于 2020-08-15T22:18:27.777 回答
1

@cliss答案的 ViewModifier考虑了设备上设置的语言的小数点分隔符。随意扩展此解决方案:

// TextField+Validator.swift

import SwiftUI
import Combine

struct TextFieldValidator: ViewModifier {
    enum ValidatorType: String {
        case decimal = "^[-]?[\\d]*(?:\\###decimalSeparator###?[\\d]*)?$"
        case number = "^\\d+$"
    }

    @Binding var goalValue: String
    
    var validatorType: ValidatorType
    
    private func validator(newValue: String) {
        let regex: String = validatorType.rawValue.replacingOccurrences(of: "###decimalSeparator###", with: Locale.current.decimalSeparator!)

        if newValue.range(of: regex, options: .regularExpression) != nil {
            self.goalValue = newValue
        } else if !self.goalValue.isEmpty {
            self.goalValue = String(newValue.prefix(self.goalValue.count - 1))
        }
    }
    
    func body(content: Content) -> some View {
        content
            .onReceive(Just(goalValue), perform: validator)
    }
}

extension TextField {
    func validator(goalValue: Binding<String>, type: TextFieldValidator.ValidatorType) -> some View {
        modifier(TextFieldValidator(goalValue: goalValue, validatorType: type))
    }
}

编号示例:

@State private var goalValue = "0"

TextField("1", text: $goalValue)
  .validator(goalValue: $goalValue, type: .number)
  .keyboardType(.numberPad)

十进制示例:

@State private var goalValue = "0,0"

TextField("1.0", text: $goalValue)
  .validator(goalValue: $goalValue, type: .decimal)
  .keyboardType(.decimalPad)
于 2021-12-05T20:47:04.910 回答
1

Jamone 将 Philip Pegden 的方法用于更强大的 NumericTextField 为我们提供了很好的服务。但是,如果在可滚动列表中使用 NumericTextField 并且部分滚动到视图之外,我发现该方法会出现一个问题。字符串的内部状态可能会丢失,从而导致滚动时出现意外行为。我还希望能够输入负数和指数部分(如 -1.6E-19 之类的数字)。我创建了一个新的 NumericTextField,它允许小数点、指数和仅包含字符串的减号选项。我还制作了一个从 onEditingChanged 错误条件触发的重新格式化函数。我的版本运行良好,但仍然可以使用更多的测试和改进。由于部分输入的数字会立即创建更新,因此部分输入通常不是 t 个数字并从数字转换器返回 nil。在转换失败时删除字符串的最后一个字符并重试似乎很简单,直到返回一个数字或没有剩余字符,在这种情况下返回 nil。一般来说,这将是最后输入的好数字。

如果在更改时发生大量计算,最好等到完成编辑后再绑定,但这不是正确的文本字段,就像最初在帖子顶部的点一样。无论如何,这是迄今为止我的版本的代码。

    //String+Numeric.swift
    import Foundation

    public extension String {
        /// Get the numeric only value from the string
        /// - Parameter allowDecimalSeparator: If `true` then a single decimal separator will be allowed in the string's mantissa.
        /// - Parameter allowMinusSign: If `true` then a single minus sign will be allowed at the beginning of the string.
        /// - Parameter allowExponent: If `true` then a single e or E  separator will be allowed in the string to start the exponent which can be a positive or negative integer
        /// - Returns: Only numeric characters and optionally a single decimal character and optional an E followed by numeric characters.
        ///            If non-numeric values were interspersed `1a2b` then the result will be `12`.
        ///            The numeric characters returned may not be valid numbers so conversions will generally be optional strings.

func numericValue(allowDecimalSeparator: Bool = true, allowNegatives: Bool = true, allowExponent: Bool = true) -> String {
    // Change parameters to single enum ?
    var hasFoundDecimal = false
    var allowMinusSign = allowNegatives // - can only be first char or first char after E (or e)
    var hasFoundExponent = !allowExponent
    var allowFindingExponent = false // initially false to avoid E as first character and then to prevent finding 2nd E
    return self.filter {
        if allowMinusSign && "-".contains($0){
            return true
        } else {
            allowMinusSign = false
            if $0.isWholeNumber {
                allowFindingExponent = true
              return true
           } else if allowDecimalSeparator && String($0) == (Locale.current.decimalSeparator ?? ".") {
              defer { hasFoundDecimal = true }
              return !hasFoundDecimal
           } else if allowExponent && !hasFoundExponent && allowFindingExponent && "eE".contains($0) {
              allowMinusSign = true
              hasFoundDecimal = true
              allowFindingExponent = false
              hasFoundExponent = true
              return true
           }
        }
        return false
    }
}

此扩展允许带有减号和一个 E 或 e 的字符串,但只能在正确的位置。

然后 NumericTextModifier a la Jamone 是

    //NumericTextModifier.swift
    import SwiftUI
    /// A modifier that observes any changes to a string, and updates that string to remove any non-numeric characters.
    /// It also will convert that string to a `NSNumber` for easy use.
    public struct NumericTextModifier: ViewModifier {
        /// Should the user be allowed to enter a decimal number, or an integer
        public let isDecimalAllowed: Bool
        public let isExponentAllowed: Bool
        public let isMinusAllowed: Bool
        /// The string that the text field is bound to
        /// A number that will be updated when the `text` is updated.
        @Binding public var number: String
        /// - Parameters:
        ///   - number:: The string 'number" that this should observe and filter
        ///   - isDecimalAllowed: Should the user be allowed to enter a decimal number, or an integer
        ///   - isExponentAllowed: Should the E (or e) be allowed in number for exponent entry
        ///   - isMinusAllowed: Should negatives be allowed with minus sign (-) at start of number
        public init( number: Binding<String>, isDecimalAllowed: Bool, isExponentAllowed: Bool, isMinusAllowed: Bool) {
            _number = number
            self.isDecimalAllowed = isDecimalAllowed
            self.isExponentAllowed = isExponentAllowed
            self.isMinusAllowed = isMinusAllowed
        }
        public func body(content: Content) -> some View {
            content
                .onChange(of: number) { newValue in
                    let numeric = newValue.numericValue(allowDecimalSeparator: isDecimalAllowed, allowNegatives: isMinusAllowed, allowExponent: isExponentAllowed).uppercased()
                    if newValue != numeric {
                        number = numeric
                    }
                }
        }
    }

    public extension View {
        /// A modifier that observes any changes to a string, and updates that string to remove any non-numeric characters.
        func numericText(number: Binding<String>, isDecimalAllowed: Bool, isMinusAllowed: Bool, isExponentAllowed: Bool) -> some View {
            modifier(NumericTextModifier( number: number, isDecimalAllowed: isDecimalAllowed, isExponentAllowed: isExponentAllowed, isMinusAllowed: isMinusAllowed))
        }
    }

NumericTextField 然后变为:

    // NumericTextField.swift
    import SwiftUI

    /// A `TextField` replacement that limits user input to numbers.
    public struct NumericTextField: View {

        /// This is what consumers of the text field will access
        @Binding private var numericText: String
    
        private let isDecimalAllowed: Bool
        private let isExponentAllowed: Bool
        private let isMinusAllowed: Bool
        
        private let title: LocalizedStringKey
        //private let formatter: NumberFormatter
        private let onEditingChanged: (Bool) -> Void
        private let onCommit: () -> Void


        /// Creates a text field with a text label generated from a localized title string.
        ///
        /// - Parameters:
        ///   - titleKey: The key for the localized title of the text field,
        ///     describing its purpose.
        ///   - numericText: The number to be displayed and edited.
        ///   - isDecimalAllowed: Should the user be allowed to enter a decimal number, or an integer
        ///   - isExponentAllowed:Should the user be allowed to enter a e or E exponent character
        ///   - isMinusAllowed:Should user be allow to enter negative numbers
        ///   - formatter: NumberFormatter to use on getting focus or losing focus used by on EditingChanged
        ///   - onEditingChanged: An action thats called when the user begins editing `text` and after the user finishes editing `text`.
        ///     The closure receives a Boolean indicating whether the text field is currently being edited.
        ///   - onCommit: An action to perform when the user performs an action (for example, when the user hits the return key) while the text field has focus.
        public init(_ titleKey: LocalizedStringKey, numericText: Binding<String>, isDecimalAllowed: Bool = true,
            isExponentAllowed: Bool = true,
            isMinusAllowed: Bool = true,
           
            onEditingChanged: @escaping (Bool) -> Void = { _ in  },
            onCommit: @escaping () -> Void = {}) {
                _numericText = numericText
           
                self.isDecimalAllowed = isDecimalAllowed || isExponentAllowed
                self.isExponentAllowed = isExponentAllowed
                self.isMinusAllowed = isMinusAllowed
                title = titleKey
                self.onEditingChanged = onEditingChanged
                self.onCommit = onCommit
        }
        
        
        public var body: some View {
            TextField(title, text: $numericText,
                onEditingChanged: { exited in
                    if !exited {
                        numericText = reformat(numericText)
                    }
                    onEditingChanged(exited)},
                onCommit: {
                    numericText = reformat(numericText)
                    onCommit() })
                .onAppear { numericText = reformat(numericText) }
                .numericText( number: $numericText, isDecimalAllowed: isDecimalAllowed, isMinusAllowed: isMinusAllowed, isExponentAllowed: isExponentAllowed )
                //.modifier(KeyboardModifier(isDecimalAllowed: isDecimalAllowed))
           
        }
    }

    func reformat(_ stringValue: String) -> String {
        if let value = NumberFormatter().number(from: stringValue) {
            let compare = value.compare(NSNumber(0.0))
                if compare == .orderedSame {
                    return "0"
                }
                if (compare == .orderedAscending) { // value negative
                    let compare = value.compare(NSNumber(-1e-3))
                    if compare != .orderedDescending {
                        let compare = value.compare(NSNumber(-1e5))
                        if compare == .orderedDescending {
                            return value.stringValue
                        }
                    }
                }
                else {
                    let compare = value.compare(NSNumber(1e5))
                    if compare == .orderedAscending {
                        let compare = value.compare(NSNumber(1e-3))
                        if compare != .orderedAscending {
                            return value.stringValue
                        }
                    }
                }
                return value.scientificStyle
        }
        return stringValue
    }

    private struct KeyboardModifier: ViewModifier {
        let isDecimalAllowed: Bool

        func body(content: Content) -> some View {
            #if os(iOS)
            return content
                .keyboardType(isDecimalAllowed ? .decimalPad : .numberPad)
            #else
            return content
            #endif
        }
    }

我直接使用了 func reformat(String) -> String 而不是格式化程序。Reformat 使用了几个格式化程序,至少对我来说更灵活。

    import Foundation

    var decimalNumberFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.allowsFloats = true
        return formatter
    }()

    var scientificFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .scientific
        formatter.allowsFloats = true
        return formatter
    }()

    extension NSNumber {
        var scientificStyle: String {
            return scientificFormatter.string(from: self) ?? description
        }
    }

我希望其中一些可以帮助其他想要在他们的应用程序中使用科学记数法和负数的人。

快乐编码。

于 2021-02-22T22:01:18.610 回答
0

扩展John M.示例以仅接受一个句点.或一个逗号,作为国际小数。

谢谢约翰 M。

struct TextFieldCharacterRestrictions: View {
    @State private var numOfPeople = ""
    
    var body: some View {
        TextField("Total number of people", text: $numOfPeople)
            .keyboardType(.decimalPad)
            .onChange(of: numOfPeople){newValue in
                let periodCount = newValue.components(separatedBy: ".").count - 1
                let commaCount = newValue.components(separatedBy: ",").count - 1
                
                if newValue.last == "." && periodCount > 1 || newValue.last == "," && commaCount > 1{
                    //it's a second period or comma, remove it
                    numOfPeople = String(newValue.dropLast())
                    // as bonus for the user, add haptic effect
                    let generator = UINotificationFeedbackGenerator()
                    generator.prepare()
                    generator.notificationOccurred(.warning)
                }else{
                    let filtered = newValue.filter { "0123456789.,".contains($0) }
                    if filtered != newValue{
                        self.numOfPeople = filtered
                    }
                }
            }
    }
}
于 2022-02-23T15:13:43.840 回答
0

更改文本:-> 值:并添加格式修饰符。

现在你可以处理你需要的一切。我会这样做:

    TextField("Total Number of people:", value: $numOfPeople, format:.number)
                .keyboardType(.numberPad)

这应该可以解决 99% 的问题。您可以在其中键入字符串,但它们会被过滤掉并且不会使您的应用程序崩溃。

于 2022-01-06T02:50:09.817 回答
0

这里作为基于 John M解决方案的变体,它避免了组合,支持任何值类型,并允许验证输出值,因此它只使用可解析和验证的输入字符串。

示例用法,保持边界值 > 0:

@State var count: Int
…
GenericEntryField(value: $count, validate: { $0 > 0 })
struct GenericEntryField<T: Equatable>: View {
    @Binding var value: T
    let stringToValue: (String) -> T?
    let validate: (T) -> Bool
    
    @State private var enteredText: String = ""
    
    var body: some View {
        return TextField(
            "",
            text: $enteredText,
            onEditingChanged: { focussed in
                if !focussed {
                    // when the textField is defocussed, reset the text back to the bound value
                    enteredText = "\(self.value)"
                }
            }
        )
            .onChange(of: enteredText) { newText in
                // whenever the text-field changes, try to convert it to a value, and validate it.
                // if so, use it (this will update the enteredText)
                if let newValue = stringToValue(newText),
                    validate(newValue) {
                    self.value = newValue
                }
            }
            .onChange(of: value) { newValue in
                 // whenever value changes externally, update the string
                enteredText = "\(newValue)"
            }
            .onAppear(perform: {
                // update the string based on value at start
                enteredText = "\(value)"
            })
    }
}
extension GenericEntryField {
    init(value: Binding<Int>, validate: @escaping (Int) -> Bool = { _ in true }) where T == Int {
        self.init(value: value, stringToValue: { Int($0) }, validate: validate)
    }
    init(value: Binding<Double>, validate: @escaping (Double) -> Bool = { _ in true }) where T == Double {
        self.init(value: value, stringToValue: { Double($0) }, validate: validate)
    }
}
于 2022-02-18T20:05:09.733 回答
0

PositiveNumbersTextField 受到此处所写内容的极大启发(谢谢!)我想出了一个稍微不同的解决方案,以满足我的需求,并使用 .onChange 修饰符回答了上面的原始问题。文本字段将只输入允许 1 个小数点、0 或空的正数。消毒剂将删除多余的小数点、开始时的多个零、开始时的小数以及任何不是数字的字符(除了 1 个小数)。这不支持负数 (-)。

struct PositiveNumbersTextField: View {

@Binding var textFieldText: String

var body: some View {
    TextField("", text: $textFieldText)
        .keyboardType(.decimalPad)
        .onChange(of: textFieldText) { text in
            textFieldText = text.sanitizeToValidPositiveNumberOrEmpty()
        }
}
}

private extension String {

func sanitizeToValidPositiveNumberOrEmpty() -> String {
    var sanitized: String
    
    // Remove multiple decimal points except the first one if exists.
    let groups = self.components(separatedBy: ".")
    if groups.count > 1 {
        sanitized = groups[0] + "." + groups.dropFirst().joined()
    } else {
        sanitized = self
    }
    
    // Remove characters that are not numbers or decimal point
    sanitized = sanitized.filter { $0.isNumber || $0 == "." }
    
    // Don't allow decimal point at start
    if sanitized.first == "." {
        sanitized.removeFirst()
    }
    
    // Remove any number after 0 (if first number is zero)
    if sanitized.first == "0" {
        var stringIndicesToRemove = [String.Index]()
        for index in 1..<sanitized.count {
            let stringIndex = sanitized.index(sanitized.startIndex, offsetBy: index)
            if sanitized[stringIndex] == "." {
                break // no need to iterate through anymore
            }
            
            stringIndicesToRemove.append(stringIndex)
        }
        
        for stringIndexToRemove in stringIndicesToRemove.reversed() {
            sanitized.remove(at: stringIndexToRemove)
        }
    }
    
    return sanitized
}
}
于 2021-11-03T07:55:37.400 回答
0

仅接受数字的 TextField:

textField("", text: Binding(
    get: {inputNum},
    set: {inputNum = $0.filter{"0123456789".contains($0)}}))
    .textFieldStyle(RoundedBorderTextFieldStyle())

将数字输入转换为 Int:

let n: Int = NumberFormatter().number(from: "0" + inputNum) as! Int
于 2022-02-19T11:36:56.760 回答