Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/vb.net/15.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
SwiftUI-如何创建只接受数字的文本字段_Swiftui_Swiftui Form - Fatal编程技术网

SwiftUI-如何创建只接受数字的文本字段

SwiftUI-如何创建只接受数字的文本字段,swiftui,swiftui-form,Swiftui,Swiftui Form,我不熟悉SwiftUI和iOS,我正在尝试创建一个只接受数字的输入字段 TextField("Total number of people", text: $numOfPeople) TextField当前允许使用字母字符,如何使其仅允许用户输入数字?tl;博士 结帐是为了更好的方式 一种方法是,您可以在TextField上设置键盘类型,这将限制人们可以键入的内容 TextField("Total number of people", text: $numOfPeopl

我不熟悉SwiftUI和iOS,我正在尝试创建一个只接受数字的输入字段

 TextField("Total number of people", text: $numOfPeople)
TextField
当前允许使用字母字符,如何使其仅允许用户输入数字?

tl;博士 结帐是为了更好的方式


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

TextField("Total number of people", text: $numOfPeople)
    .keyboardType(.numberPad)
苹果的文档可以找到,你可以看到所有支持的键盘类型的列表

但是,该方法只是第一步,作为唯一的解决方案,并不理想:

  • iPad没有数字广告,所以这种方法在iPad上不起作用
  • 如果用户使用的是硬件键盘,则此方法将不起作用
  • 它不检查用户输入的内容。用户可以将非数值复制/粘贴到文本字段中
  • 您应该清理输入的数据,并确保它纯粹是数字


    对于执行该签出的解决方案。他在解释如何清理数据及其工作原理方面做得很好。

    尽管显示数字键盘是一个好的第一步,但实际上并不能防止输入坏数据:

  • 用户可以将非数字文本粘贴到文本字段中
  • iPad用户仍将获得完整的键盘
  • 任何连接蓝牙键盘的人都可以键入任何内容
  • 您真正想做的是清理输入,如下所示:

    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
                    }
            }
        }
    }
    
    struct MyView : View {
        @State var doubleValue : Double = 1.56
    
        var body: some View {        
            return HStack {
                 Text("Numeric field:")
                 NumberEntryField(value: self.$doubleValue)   
                }
          }
    }
    
    每当
    numfopeople
    发生更改时,非数值将被过滤掉,并比较过滤后的值,以查看是否应再次更新
    numfopeople
    ,用过滤后的输入覆盖错误的输入

    请注意,
    Just
    发布服务器要求您导入联合收割机

    编辑:

    解释<代码>只是 Publisher,考虑下面的概念大纲,当在代码>文本字段< /代码>中更改值时发生什么:

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

  • 查看以了解更多信息。

    您不需要使用
    组合
    接收
    ,也可以使用以下代码:

    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())
        }
    }
    

    不幸的是,还有一个小的闪烁,因此您也可以在很短的时间内看到不允许的字符(在我看来,这比使用
    组合
    的方式要短一些)

    另一种方法可能是创建一个包装TextField视图的视图,并保存两个值:一个保存输入字符串的私有变量,和一个可绑定的值,该值保存双重等效值。每次用户键入一个字符时,它都会尝试更新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)"})
        }
    }
    
    您可以这样使用它:

    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
                    }
            }
        }
    }
    
    struct MyView : View {
        @State var doubleValue : Double = 1.56
    
        var body: some View {        
            return HStack {
                 Text("Numeric field:")
                 NumberEntryField(value: self.$doubleValue)   
                }
          }
    }
    

    这是一个简单的示例-您可能希望添加功能,以显示输入错误的警告,可能还有边界检查等。

    @John M.的
    ViewModifier
    版本

    导入联合收割机
    导入快捷键
    公共结构NumberOnlyViewModifier:ViewModifier{
    @绑定变量文本:字符串
    公共初始化(文本:绑定){
    self.\u text=文本
    }
    公共函数体(内容:content)->一些视图{
    内容
    .键盘类型(.numberPad)
    .onReceive(Just(text)){newValue in
    让filtered=newValue.filter{“0123456789”。包含($0)}
    如果已筛选!=newValue{
    self.text=已过滤
    }
    }
    }
    }
    
    大多数答案都有一些明显的缺点。菲利普的是迄今为止最好的。大多数其他答案在键入非数字字符时不会过滤掉这些字符。相反,您必须等到用户完成编辑之后,然后他们更新文本以删除非数字字符。然后,下一个常见问题是,当输入语言不使用ASCII 0-9字符作为数字时,它们不处理数字

    我提出了一个类似于Philip的解决方案,但更适合生产

    首先,您需要一种从字符串中正确过滤非数字字符的方法,这种方法可以与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)
        }
    }
    

    首先在这里发布,所以请原谅任何错误。在我目前的项目中,我一直在努力解决这个问题。许多答案都很有效,但只适用于特定问题,就我而言,没有一个答案满足所有要求

    具体而言,我需要:

  • 仅数字用户输入,i
    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
                    }
                }
            }
    }
    
    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)"
          })
      }
    }
    
    @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))
        }
    }
    
    @State private var myValue: Int
    // ...
    TextField("number", text: Binding(
        get: { String(myValue) }, 
        set: { myValue = Int($0) ?? 0 }
    ))
    
    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())
        }
    }
    
        //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
        }
    }
    
        //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.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
            }
        }
    
        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
            }
        }