我如何使用 Combine 来跟踪 UIViewRepresentable class 中的 UITextField 更改?
How can I use Combine to track UITextField changes in a UIViewRepresentable class?
我创建了一个自定义文本字段,我想利用 Combine。为了在我的文本字段中的文本更改时收到通知,我目前使用自定义修饰符。它运行良好,但我希望这段代码可以在我的 CustomTextField 结构中。
我的 CustomTextField 结构符合 UIViewRepresentable。在这个结构中,有一个名为 Coordinator 的 NSObject class,它符合 UITextFieldDelegate。
我已经在使用其他 UITextField 委托方法,但无法找到完全符合我已经使用自定义修饰符执行的操作的方法。有些方法很接近,但并不完全按照我希望的方式运行。无论如何,我觉得最好将这个新的自定义 textFieldDidChange 方法放在 Coordinator class 中。
这是我的自定义修饰符
private let textFieldDidChange = NotificationCenter.default
.publisher(for: UITextField.textDidChangeNotification)
.map { [=11=].object as! UITextField}
struct CustomModifer: ViewModifier {
func body(content: Content) -> some View {
content
.tag(1)
.onReceive(textFieldDidChange) { data in
//do something
}
}
}
我的 CustomTextField 在 SwiftUI 视图中使用,并附加了我的自定义修饰符。当文本字段发生变化时,我能够做一些事情。修改器也使用 Combine。它工作得很好,但我不希望这个功能以修饰符的形式存在。我想在我的协调器 class 中使用它以及我的 UITextFieldDelegate 方法。
这是我的 CustomTextField
struct CustomTextField: UIViewRepresentable {
var isFirstResponder: Bool = false
@EnvironmentObject var authenticationViewModel: AuthenticationViewModel
func makeCoordinator() -> Coordinator {
return Coordinator(authenticationViewModel: self._authenticationViewModel)
}
class Coordinator: NSObject, UITextFieldDelegate {
var didBecomeFirstResponder = false
@EnvironmentObject var authenticationViewModel: AuthenticationViewModel
init(authenticationViewModel: EnvironmentObject<AuthenticationViewModel>)
{
self._authenticationViewModel = authenticationViewModel
}
// Limit the amount of characters that can be typed in the field
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentText = textField.text ?? ""
guard let stringRange = Range(range, in: currentText) else { return false }
let updatedText = currentText.replacingCharacters(in: stringRange, with: string)
return updatedText.count <= 14
}
/* I want to put my textFieldDidChange method right here */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * */
func textFieldDidEndEditing(_ textField: UITextField) {
textField.resignFirstResponder()
textField.endEditing(true)
}
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
textField.placeholder = context.coordinator.authenticationViewModel.placeholder
textField.font = .systemFont(ofSize: 33, weight: .bold)
textField.keyboardType = .numberPad
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
let textField = uiView
textField.text = self.authenticationViewModel.text
}
}
struct CustomTextField_Previews: PreviewProvider {
static var previews: some View {
CustomTextField()
.previewLayout(.fixed(width: 270, height: 55))
.previewDisplayName("Custom Textfield")
.previewDevice(.none)
}
}
我一直在观看有关 Combine 的视频,我想开始在我正在构建的新应用程序中使用它。我真的认为在这种情况下使用它是正确的,但仍然不太确定如何做到这一点。我真的很感激一个例子。
总结一下:
我想将一个名为 textFieldDidChange 的函数添加到我的协调器 class,并且它应该在每次我的文本字段发生更改时触发。它必须利用 Combine。
提前致谢
我对你的问题有点困惑,因为你在谈论 UITextField
和 SwiftUI。
像这样的事情怎么样?它不使用 UITextField
而是使用 SwiftUI 的 TextField
对象。
只要您的 ContentView
中的 TextField
发生变化,此 class 就会通知您。
class CustomModifier: ObservableObject {
var observedValue: String = "" {
willSet(observedValue) {
print(observedValue)
}
}
}
确保您在修改器 class 上使用 @ObservedObject
并且您将能够看到更改。
struct ContentView: View {
@ObservedObject var modifier = CustomModifier()
var body: some View {
TextField("Input:", text: $modifier.observedValue)
}
}
如果这完全不符合您的要求,那么我可以推荐以下文章吗?这可能会有所帮助?
https://medium.com/@valv0/textfield-and-uiviewrepresentable-46a8d3ec48e2
更新答案
在查看您更新后的问题后,我意识到我原来的答案可能需要一些清理。我已将模型和协调器合并为一个 class,虽然它适用于我的示例,但并不总是可行或可取的。如果model和coordinator不能相同,那么就不能依赖model属性的didSet方法来更新textField。因此,我正在使用模型中的 @Published
变量免费获得的 Combine 发布者。
我们需要做的关键事情是:
通过保持 model.text
和 textField.text
同步来建立单一的事实来源
使用 @Published
属性 包装器提供的发布者在 model.text
更改时更新 textField.text
在 textField
上使用 .addTarget(:action:for)
方法在 textfield.text
更改时更新 model.text
当我们的模型改变时执行一个名为 textDidChange
的闭包。
(我更喜欢在#1.2 中使用 .addTarget
而不是通过 NotificationCenter
,因为它的代码更少,可以立即运行,而且 UIKit 的用户都知道它)。
这里是一个更新的例子,显示了这个工作原理:
演示
import SwiftUI
import Combine
// Example view showing that `model.text` and `textField.text`
// stay in sync with one another
struct CustomTextFieldDemo: View {
@ObservedObject var model = Model()
var body: some View {
VStack {
// The model's text can be used as a property
Text("The text is \"\(model.text)\"")
// or as a binding,
TextField(model.placeholder, text: $model.text)
.disableAutocorrection(true)
.padding()
.border(Color.black)
// or the model itself can be passed to a CustomTextField
CustomTextField().environmentObject(model)
.padding()
.border(Color.black)
}
.frame(height: 100)
.padding()
}
}
型号
class Model: ObservableObject {
@Published var text = ""
var placeholder = "Placeholder"
}
查看
struct CustomTextField: UIViewRepresentable {
@EnvironmentObject var model: Model
func makeCoordinator() -> CustomTextField.Coordinator {
Coordinator(model: model)
}
func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
let textField = UITextField()
// Set the coordinator as the textField's delegate
textField.delegate = context.coordinator
// Set up textField's properties
textField.text = context.coordinator.model.text
textField.placeholder = context.coordinator.model.placeholder
textField.autocorrectionType = .no
// Update model.text when textField.text is changed
textField.addTarget(context.coordinator,
action: #selector(context.coordinator.textFieldDidChange),
for: .editingChanged)
// Update textField.text when model.text is changed
// The map step is there because .assign(to:on:) complains
// if you try to assign a String to textField.text, which is a String?
// Note that assigning textField.text with .assign(to:on:)
// does NOT trigger a UITextField.Event.editingChanged
let sub = context.coordinator.model.$text.receive(on: RunLoop.main)
.map { Optional([=12=]) }
.assign(to: \UITextField.text, on: textField)
context.coordinator.subscribers.append(sub)
// Become first responder
textField.becomeFirstResponder()
return textField
}
func updateUIView(_ textField: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
// If something needs to happen when the view updates
}
}
View.Coordinator
extension CustomTextField {
class Coordinator: NSObject, UITextFieldDelegate, ObservableObject {
@ObservedObject var model: Model
var subscribers: [AnyCancellable] = []
// Make subscriber which runs textDidChange closure whenever model.text changes
init(model: Model) {
self.model = model
let sub = model.$text.receive(on: RunLoop.main).sink(receiveValue: textDidChange)
subscribers.append(sub)
}
// Cancel subscribers when Coordinator is deinitialized
deinit {
for sub in subscribers {
sub.cancel()
}
}
// Any code that needs to be run when model.text changes
var textDidChange: (String) -> Void = { text in
print("Text changed to \"\(text)\"")
// * * * * * * * * * * //
// Put your code here //
// * * * * * * * * * * //
}
// Update model.text when textField.text is changed
@objc func textFieldDidChange(_ textField: UITextField) {
model.text = textField.text ?? ""
}
// Example UITextFieldDelegate method
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
}
原答案
听起来你有几个目标:
- 使用
UITextField
这样您就可以使用像 .becomeFirstResponder()
这样的功能
- 文本更改时执行操作
- 通知其他 SwiftUI 视图文本已更改
我认为您可以使用单个模型 class 和 UIViewRepresentable
结构来满足所有这些要求。我以这种方式构建代码的原因是为了让您拥有单一的真实来源 (model.text
),它可以与采用 String
或 Binding<String>
的其他 SwiftUI 视图互换使用。
型号
class MyTextFieldModel: NSObject, UITextFieldDelegate, ObservableObject {
// Must be weak, so that we don't have a strong reference cycle
weak var textField: UITextField?
// The @Published property wrapper just makes a Combine Publisher for the text
@Published var text: String = "" {
// If the model's text property changes, update the UITextField
didSet {
textField?.text = text
}
}
// If the UITextField's text property changes, update the model
@objc func textFieldDidChange() {
text = textField?.text ?? ""
// Put your code that needs to run on text change here
print("Text changed to \"\(text)\"")
}
// Example UITextFieldDelegate method
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
查看
struct MyTextField: UIViewRepresentable {
@ObservedObject var model: MyTextFieldModel
func makeUIView(context: UIViewRepresentableContext<MyTextField>) -> UITextField {
let textField = UITextField()
// Give the model a reference to textField
model.textField = textField
// Set the model as the textField's delegate
textField.delegate = model
// TextField setup
textField.text = model.text
textField.placeholder = "Type in this UITextField"
// Call the model's textFieldDidChange() method on change
textField.addTarget(model, action: #selector(model.textFieldDidChange), for: .editingChanged)
// Become first responder
textField.becomeFirstResponder()
return textField
}
func updateUIView(_ textField: UITextField, context: UIViewRepresentableContext<MyTextField>) {
// If something needs to happen when the view updates
}
}
如果你不需要上面的#3,你可以替换
@ObservedObject var model: MyTextFieldModel
和
@ObservedObject private var model = MyTextFieldModel()
演示
这是展示所有这些工作的演示视图
struct MyTextFieldDemo: View {
@ObservedObject var model = MyTextFieldModel()
var body: some View {
VStack {
// The model's text can be used as a property
Text("The text is \"\(model.text)\"")
// or as a binding,
TextField("Type in this TextField", text: $model.text)
.padding()
.border(Color.black)
// but the model itself should only be used for one wrapped UITextField
MyTextField(model: model)
.padding()
.border(Color.black)
}
.frame(height: 100)
// Any view can subscribe to the model's text publisher
.onReceive(model.$text) { text in
print("I received the text \"\(text)\"")
}
}
}
我还需要在 SwiftUI 中使用 UITextField,所以我尝试了以下代码:
struct MyTextField: UIViewRepresentable {
private var placeholder: String
@Binding private var text: String
private var textField = UITextField()
init(_ placeholder: String, text: Binding<String>) {
self.placeholder = placeholder
self._text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(textField: self.textField, text: self._text)
}
func makeUIView(context: Context) -> UITextField {
textField.placeholder = self.placeholder
textField.font = UIFont.systemFont(ofSize: 20)
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
}
class Coordinator: NSObject {
private var dispose = Set<AnyCancellable>()
@Binding var text: String
init(textField: UITextField, text: Binding<String>) {
self._text = text
super.init()
NotificationCenter.default
.publisher(for: UITextField.textDidChangeNotification, object: textField)
.compactMap { [=10=].object as? UITextField }
.compactMap { [=10=].text }
.receive(on: RunLoop.main)
.assign(to: \.text, on: self)
.store(in: &dispose)
}
}
}
struct ContentView: View {
@State var text: String = ""
var body: some View {
VStack {
MyTextField("placeholder", text: self.$text).padding()
Text(self.text).foregroundColor(.red).padding()
}
}
}
我创建了一个自定义文本字段,我想利用 Combine。为了在我的文本字段中的文本更改时收到通知,我目前使用自定义修饰符。它运行良好,但我希望这段代码可以在我的 CustomTextField 结构中。
我的 CustomTextField 结构符合 UIViewRepresentable。在这个结构中,有一个名为 Coordinator 的 NSObject class,它符合 UITextFieldDelegate。
我已经在使用其他 UITextField 委托方法,但无法找到完全符合我已经使用自定义修饰符执行的操作的方法。有些方法很接近,但并不完全按照我希望的方式运行。无论如何,我觉得最好将这个新的自定义 textFieldDidChange 方法放在 Coordinator class 中。
这是我的自定义修饰符
private let textFieldDidChange = NotificationCenter.default
.publisher(for: UITextField.textDidChangeNotification)
.map { [=11=].object as! UITextField}
struct CustomModifer: ViewModifier {
func body(content: Content) -> some View {
content
.tag(1)
.onReceive(textFieldDidChange) { data in
//do something
}
}
}
我的 CustomTextField 在 SwiftUI 视图中使用,并附加了我的自定义修饰符。当文本字段发生变化时,我能够做一些事情。修改器也使用 Combine。它工作得很好,但我不希望这个功能以修饰符的形式存在。我想在我的协调器 class 中使用它以及我的 UITextFieldDelegate 方法。
这是我的 CustomTextField
struct CustomTextField: UIViewRepresentable {
var isFirstResponder: Bool = false
@EnvironmentObject var authenticationViewModel: AuthenticationViewModel
func makeCoordinator() -> Coordinator {
return Coordinator(authenticationViewModel: self._authenticationViewModel)
}
class Coordinator: NSObject, UITextFieldDelegate {
var didBecomeFirstResponder = false
@EnvironmentObject var authenticationViewModel: AuthenticationViewModel
init(authenticationViewModel: EnvironmentObject<AuthenticationViewModel>)
{
self._authenticationViewModel = authenticationViewModel
}
// Limit the amount of characters that can be typed in the field
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentText = textField.text ?? ""
guard let stringRange = Range(range, in: currentText) else { return false }
let updatedText = currentText.replacingCharacters(in: stringRange, with: string)
return updatedText.count <= 14
}
/* I want to put my textFieldDidChange method right here */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * */
func textFieldDidEndEditing(_ textField: UITextField) {
textField.resignFirstResponder()
textField.endEditing(true)
}
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
textField.placeholder = context.coordinator.authenticationViewModel.placeholder
textField.font = .systemFont(ofSize: 33, weight: .bold)
textField.keyboardType = .numberPad
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
let textField = uiView
textField.text = self.authenticationViewModel.text
}
}
struct CustomTextField_Previews: PreviewProvider {
static var previews: some View {
CustomTextField()
.previewLayout(.fixed(width: 270, height: 55))
.previewDisplayName("Custom Textfield")
.previewDevice(.none)
}
}
我一直在观看有关 Combine 的视频,我想开始在我正在构建的新应用程序中使用它。我真的认为在这种情况下使用它是正确的,但仍然不太确定如何做到这一点。我真的很感激一个例子。
总结一下:
我想将一个名为 textFieldDidChange 的函数添加到我的协调器 class,并且它应该在每次我的文本字段发生更改时触发。它必须利用 Combine。
提前致谢
我对你的问题有点困惑,因为你在谈论 UITextField
和 SwiftUI。
像这样的事情怎么样?它不使用 UITextField
而是使用 SwiftUI 的 TextField
对象。
只要您的 ContentView
中的 TextField
发生变化,此 class 就会通知您。
class CustomModifier: ObservableObject {
var observedValue: String = "" {
willSet(observedValue) {
print(observedValue)
}
}
}
确保您在修改器 class 上使用 @ObservedObject
并且您将能够看到更改。
struct ContentView: View {
@ObservedObject var modifier = CustomModifier()
var body: some View {
TextField("Input:", text: $modifier.observedValue)
}
}
如果这完全不符合您的要求,那么我可以推荐以下文章吗?这可能会有所帮助?
https://medium.com/@valv0/textfield-and-uiviewrepresentable-46a8d3ec48e2
更新答案
在查看您更新后的问题后,我意识到我原来的答案可能需要一些清理。我已将模型和协调器合并为一个 class,虽然它适用于我的示例,但并不总是可行或可取的。如果model和coordinator不能相同,那么就不能依赖model属性的didSet方法来更新textField。因此,我正在使用模型中的 @Published
变量免费获得的 Combine 发布者。
我们需要做的关键事情是:
通过保持
model.text
和textField.text
同步来建立单一的事实来源使用
@Published
属性 包装器提供的发布者在model.text
更改时更新textField.text
在
textField
上使用.addTarget(:action:for)
方法在textfield.text
更改时更新model.text
当我们的模型改变时执行一个名为
textDidChange
的闭包。
(我更喜欢在#1.2 中使用 .addTarget
而不是通过 NotificationCenter
,因为它的代码更少,可以立即运行,而且 UIKit 的用户都知道它)。
这里是一个更新的例子,显示了这个工作原理:
演示
import SwiftUI
import Combine
// Example view showing that `model.text` and `textField.text`
// stay in sync with one another
struct CustomTextFieldDemo: View {
@ObservedObject var model = Model()
var body: some View {
VStack {
// The model's text can be used as a property
Text("The text is \"\(model.text)\"")
// or as a binding,
TextField(model.placeholder, text: $model.text)
.disableAutocorrection(true)
.padding()
.border(Color.black)
// or the model itself can be passed to a CustomTextField
CustomTextField().environmentObject(model)
.padding()
.border(Color.black)
}
.frame(height: 100)
.padding()
}
}
型号
class Model: ObservableObject {
@Published var text = ""
var placeholder = "Placeholder"
}
查看
struct CustomTextField: UIViewRepresentable {
@EnvironmentObject var model: Model
func makeCoordinator() -> CustomTextField.Coordinator {
Coordinator(model: model)
}
func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
let textField = UITextField()
// Set the coordinator as the textField's delegate
textField.delegate = context.coordinator
// Set up textField's properties
textField.text = context.coordinator.model.text
textField.placeholder = context.coordinator.model.placeholder
textField.autocorrectionType = .no
// Update model.text when textField.text is changed
textField.addTarget(context.coordinator,
action: #selector(context.coordinator.textFieldDidChange),
for: .editingChanged)
// Update textField.text when model.text is changed
// The map step is there because .assign(to:on:) complains
// if you try to assign a String to textField.text, which is a String?
// Note that assigning textField.text with .assign(to:on:)
// does NOT trigger a UITextField.Event.editingChanged
let sub = context.coordinator.model.$text.receive(on: RunLoop.main)
.map { Optional([=12=]) }
.assign(to: \UITextField.text, on: textField)
context.coordinator.subscribers.append(sub)
// Become first responder
textField.becomeFirstResponder()
return textField
}
func updateUIView(_ textField: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
// If something needs to happen when the view updates
}
}
View.Coordinator
extension CustomTextField {
class Coordinator: NSObject, UITextFieldDelegate, ObservableObject {
@ObservedObject var model: Model
var subscribers: [AnyCancellable] = []
// Make subscriber which runs textDidChange closure whenever model.text changes
init(model: Model) {
self.model = model
let sub = model.$text.receive(on: RunLoop.main).sink(receiveValue: textDidChange)
subscribers.append(sub)
}
// Cancel subscribers when Coordinator is deinitialized
deinit {
for sub in subscribers {
sub.cancel()
}
}
// Any code that needs to be run when model.text changes
var textDidChange: (String) -> Void = { text in
print("Text changed to \"\(text)\"")
// * * * * * * * * * * //
// Put your code here //
// * * * * * * * * * * //
}
// Update model.text when textField.text is changed
@objc func textFieldDidChange(_ textField: UITextField) {
model.text = textField.text ?? ""
}
// Example UITextFieldDelegate method
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
}
原答案
听起来你有几个目标:
- 使用
UITextField
这样您就可以使用像.becomeFirstResponder()
这样的功能
- 文本更改时执行操作
- 通知其他 SwiftUI 视图文本已更改
我认为您可以使用单个模型 class 和 UIViewRepresentable
结构来满足所有这些要求。我以这种方式构建代码的原因是为了让您拥有单一的真实来源 (model.text
),它可以与采用 String
或 Binding<String>
的其他 SwiftUI 视图互换使用。
型号
class MyTextFieldModel: NSObject, UITextFieldDelegate, ObservableObject {
// Must be weak, so that we don't have a strong reference cycle
weak var textField: UITextField?
// The @Published property wrapper just makes a Combine Publisher for the text
@Published var text: String = "" {
// If the model's text property changes, update the UITextField
didSet {
textField?.text = text
}
}
// If the UITextField's text property changes, update the model
@objc func textFieldDidChange() {
text = textField?.text ?? ""
// Put your code that needs to run on text change here
print("Text changed to \"\(text)\"")
}
// Example UITextFieldDelegate method
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
查看
struct MyTextField: UIViewRepresentable {
@ObservedObject var model: MyTextFieldModel
func makeUIView(context: UIViewRepresentableContext<MyTextField>) -> UITextField {
let textField = UITextField()
// Give the model a reference to textField
model.textField = textField
// Set the model as the textField's delegate
textField.delegate = model
// TextField setup
textField.text = model.text
textField.placeholder = "Type in this UITextField"
// Call the model's textFieldDidChange() method on change
textField.addTarget(model, action: #selector(model.textFieldDidChange), for: .editingChanged)
// Become first responder
textField.becomeFirstResponder()
return textField
}
func updateUIView(_ textField: UITextField, context: UIViewRepresentableContext<MyTextField>) {
// If something needs to happen when the view updates
}
}
如果你不需要上面的#3,你可以替换
@ObservedObject var model: MyTextFieldModel
和
@ObservedObject private var model = MyTextFieldModel()
演示
这是展示所有这些工作的演示视图
struct MyTextFieldDemo: View {
@ObservedObject var model = MyTextFieldModel()
var body: some View {
VStack {
// The model's text can be used as a property
Text("The text is \"\(model.text)\"")
// or as a binding,
TextField("Type in this TextField", text: $model.text)
.padding()
.border(Color.black)
// but the model itself should only be used for one wrapped UITextField
MyTextField(model: model)
.padding()
.border(Color.black)
}
.frame(height: 100)
// Any view can subscribe to the model's text publisher
.onReceive(model.$text) { text in
print("I received the text \"\(text)\"")
}
}
}
我还需要在 SwiftUI 中使用 UITextField,所以我尝试了以下代码:
struct MyTextField: UIViewRepresentable {
private var placeholder: String
@Binding private var text: String
private var textField = UITextField()
init(_ placeholder: String, text: Binding<String>) {
self.placeholder = placeholder
self._text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(textField: self.textField, text: self._text)
}
func makeUIView(context: Context) -> UITextField {
textField.placeholder = self.placeholder
textField.font = UIFont.systemFont(ofSize: 20)
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
}
class Coordinator: NSObject {
private var dispose = Set<AnyCancellable>()
@Binding var text: String
init(textField: UITextField, text: Binding<String>) {
self._text = text
super.init()
NotificationCenter.default
.publisher(for: UITextField.textDidChangeNotification, object: textField)
.compactMap { [=10=].object as? UITextField }
.compactMap { [=10=].text }
.receive(on: RunLoop.main)
.assign(to: \.text, on: self)
.store(in: &dispose)
}
}
}
struct ContentView: View {
@State var text: String = ""
var body: some View {
VStack {
MyTextField("placeholder", text: self.$text).padding()
Text(self.text).foregroundColor(.red).padding()
}
}
}