SwiftUI 验证文本字段中的输入
Posted
技术标签:
【中文标题】SwiftUI 验证文本字段中的输入【英文标题】:SwiftUI validate input in textfields 【发布时间】: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 方法。我在操场上做了一些测试并确认了这种行为。但是,当我使用 ObservableObject
和 Published
变量时,情况似乎有所不同。
如何防止 didSet 方法递归调用自身?
我尝试了post 中的示例,但没有一个有效。从那时起,Apple 可能已经改变了一些事情,所以这篇文章不是那个重复的。
此外,在遇到无效字符时在 didSet
方法中将文本设置回 oldValue
意味着如果用户粘贴文本,则整个文本将被删除,而不是仅删除无效字符。所以这个选项不起作用。
【问题讨论】:
【参考方案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】:由于在设置值时总是调用didSet
和willSet
,并且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
【讨论】:
以上是关于SwiftUI 验证文本字段中的输入的主要内容,如果未能解决你的问题,请参考以下文章
ForEach 通过多个 TextField 来验证 SwiftUI 中是不是为空
在 SwiftUI 中的 forEach 期间将文本字段输入添加到数组