SwiftUI验证文本字段中的输入



我正试图通过使用正则表达式删除某些字符来验证TextField中的用户输入。不幸的是,textvar的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方法。我在操场上做了一些测试,证实了这种行为。然而,当我使用ObservableObjectPublished变量时,情况似乎有所不同。

如何防止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.
}

尝试验证TextFieldonRecive方法中想要的内容,如下所示:

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

由于在设置值时总是调用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)
}
}

最新更新