【问题标题】:SwiftUI validate input in textfieldsSwiftUI 验证文本字段中的输入
【发布时间】:2020-04-07 05:04:17
【问题描述】:

我正在尝试通过使用正则表达式删除某些字符来验证 TextField 中的用户输入。不幸的是,我遇到了 text var 递归调用自身的 didSet 方法的问题。

import SwiftUI
import Combine

class TextValidator: ObservableObject {

    @Published var text = "" {
        didSet {
            print("didSet")
            text = text.replacingOccurrences(
                of: "\\W", with: "", options: .regularExpression
            ) // `\W` is an escape sequence that matches non-word characters.
        }
    }

}


struct ContentView: View {

    @ObservedObject var textValidator = TextValidator()

    var body: some View {
        TextField("Type Here", text: $textValidator.text)
            .padding(.horizontal, 20.0)
            .textFieldStyle(RoundedBorderTextFieldStyle())

    }
}

swift docs(参见 AudioChannel 结构)中,Apple 提供了一个示例,其中在其自己的 didSet 方法中重新分配了一个属性,并明确指出这不会导致再次调用 didSet 方法。我在操场上做了一些测试并确认了这种行为。但是,当我使用 ObservableObjectPublished 变量时,情况似乎有所不同。

如何防止 didSet 方法递归调用自身?

我尝试了post 中的示例,但没有一个有效。从那时起,Apple 可能已经改变了一些事情,所以这篇文章不是那个重复的。

此外,在遇到无效字符时在 didSet 方法中将文本设置回 oldValue 意味着如果用户粘贴文本,则整个文本将被删除,而不是仅删除无效字符。所以这个选项不起作用。

【问题讨论】:

    标签: swift swiftui


    【解决方案1】:

    尝试在 TextField onRecive 方法中验证您想要的内容,如下所示:

    class TextValidator: ObservableObject {
    
        @Published var text = ""
    
    }
    
    
    struct ContentView: View {
    
        @ObservedObject var textValidator = TextValidator()
        var body: some View {
            TextField("Type Here", text: $textValidator.text)
                .padding(.horizontal, 20.0)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .onReceive(Just(textValidator.text)) { newValue in
                    let value = newValue.replacingOccurrences(
                        of: "\\W", with: "", options: .regularExpression)
                    if value != newValue {
                        self.textValidator.text = value
                    }
                    print(newValue)
            }
        }
    }
    

    【讨论】:

    • 哇!我从来不知道修饰符的存在!
    • 哇!这是我一直在寻找的解决方案。现在需要查看组合,因为可能有很多宝石等待被发现......非常感谢
    【解决方案2】:

    从 SwiftUI 2 开始,您可以使用 onChange 方法检查输入并在那里进行任何验证或更改:

    TextField("", value: $text)
        .onChange(of: text) { [text] newValue in
             // do any validation or alteration here.
             // 'text' is the old value, 'newValue' is the new one.
        }
    

    【讨论】:

      【解决方案3】:

      这是使用代理绑定的可能方法,它仍然允许分离视图和视图模型逻辑

      class TextValidator: ObservableObject {
      
          @Published var text = ""
      
          func validate(_ value: String) -> String {
              value.replacingOccurrences(
                      of: "\\W", with: "", options: .regularExpression
                  )
          }
      }
      
      
      struct ContentView: View {
      
          @ObservedObject var textValidator = TextValidator()
      
          var body: some View {
              let validatingText = Binding<String>(
                      get: { self.textValidator.text },
                      set: { self.textValidator.text = self.textValidator.validate($0) }
                      )
              return TextField("Type Here", text: validatingText)
                  .padding(.horizontal, 20.0)
                  .textFieldStyle(RoundedBorderTextFieldStyle())
      
          }
      }
      

      【讨论】:

      • 谢谢,这行得通。你为什么递归调用我的代码版本中的 didSet 方法?另外,你能解释一下你的代码吗?
      【解决方案4】:

      由于在设置值时总是调用didSetwillSet,并且objectWillChange 会触发对TextField 的更新(这会再次触发didSet),因此当基础值无条件更新时会创建一个循环在didSet.

      更新基础值有条件地中断循环。 例如:

      import Combine
      
      class TextValidator: ObservableObject {
          @Published var text = "" {
              didSet {
                  if oldValue == text || text == acceptableValue(oldValue)  {
                      return
                  }
                  text = acceptableValue(text)
              }
          }
          
          var acceptableValue: (String) -> String = { $0 }
      }
      

      import SwiftUI
      
      struct TestingValidation: View {
          @StateObject var textValidator: TextValidator = {
              let o = TextValidator()
              o.acceptableValue = { $0.replacingOccurrences(
                  of: "\\W", with: "", options: .regularExpression) }
              return o
          }()
          
          @StateObject var textValidator2: TextValidator = {
              let o = TextValidator()
              o.acceptableValue = { $0.replacingOccurrences(
                      of: "\\D", with: "", options: .regularExpression) }
              return o
          }()
          
         
          var body: some View {
                  VStack {
                      Text("Word characters only")
                      TextField("Type here", text: $textValidator.text)
                      
                      Text("Digits only")
                      TextField("Type here", text: $textValidator2.text)
                  }
                  .padding(.horizontal, 20.0)
                  .textFieldStyle(RoundedBorderTextFieldStyle())
                  .disableAutocorrection(true)
                  .autocapitalization(.none)
          }
      }
      

      【讨论】:

        【解决方案5】:

        2021 | SwiftUI 2

        自定义扩展用法:

        TextField("New Branch name", text: $model.newNameUnified)
            .ignoreSymbols( symbols: [" ", "\n"], string: $model.newNameUnified )
        

        扩展名:

        @available(OSX 11.0, *)
        public extension TextField {
            func ignoreSymbols(symbols: [Character], string: Binding<String>) -> some View {
                 self.modifier( IgnoreSymbols(symbols: symbols, string: string)  )
            }
        }
        
        @available(OSX 11.0, *)
        public struct IgnoreSymbols: ViewModifier {
            var symbols: [Character]
            var string: Binding<String>
            
            public func body (content: Content) -> some View
            {
                content.onChange(of: string.wrappedValue) { value in
                    var newValue = value
                
                    for symbol in symbols {
                        newValue = newValue.replace(of: "\(symbol)", to: "")
                    }
                
                    if value != newValue {
                        string.wrappedValue = newValue
                    }
                }
            }
        }
        

        【讨论】:

          【解决方案6】:

          这是我的想法:

          struct ValidatableTextField: View {
              let placeholder: String
              @State private var text = ""
              var validation: (String) -> Bool
            
              @Binding private var sourceText: String
            
              init(_ placeholder: String, text: Binding<String>, validation: @escaping (String) -> Bool) {
                  self.placeholder = placeholder
                  self.validation = validation
                  self._sourceText = text
              
                  self.text = text.wrappedValue
              }
            
              var body: some View {
                  TextField(placeholder, text: $text)
                      .onChange(of: text) { newValue in
                          if validation(newValue) {
                              self.sourceText = newValue
                          } else {
                            self.text = sourceText
                          }
                      }
              }
          }
          

          用法:

          ValidatableTextField("Placeholder", text: $text, validation: { !$0.contains("%") })
          

          注意:此代码不能专门解决您的问题,但会展示如何处理一般验证。

          更改正文以解决您的问题:

          TextField(placeholder, text: $text)
              .onChange(of: text) { newValue in
                  let value = newValue.replacingOccurrences(of: "\\W", with: "", options: .regularExpression)
                  if value != newValue {
                      self.sourceText = newValue
                      self.text = sourceText
                  }
              }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-11-29
            • 2023-03-16
            相关资源
            最近更新 更多