我正试图通过使用正则表达式删除某些字符来验证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文档中(请参阅AudioChannel结构(,Apple提供了一个示例,其中在自己的didSet方法中重新分配了一个属性,并明确指出这不会导致再次调用didSet方法。我在操场上做了一些测试,证实了这种行为。然而,当我使用ObservableObject
和Published
变量时,情况似乎有所不同。
如何防止didSet方法递归调用自己
我试过这篇文章中的例子,但都不起作用。从那时起,苹果可能已经改变了一些事情,所以这篇文章并不是那篇文章的副本。
此外,在遇到无效字符时,在didSet
方法中将文本设置回oldValue
意味着,如果用户粘贴文本,则将删除整个文本,而不是仅删除无效字符。所以这种选择是行不通的。
由于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.
}
尝试验证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)
}
}
}
这里有一种使用代理绑定的可能方法,它仍然允许视图&视图模型逻辑
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())
}
}
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
}
}
}
}
以下是我的想法:
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
}
}
由于在设置值时总是调用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)
}
}