从按钮操作中更改时未触发 SwiftUI 绑定
Posted
技术标签:
【中文标题】从按钮操作中更改时未触发 SwiftUI 绑定【英文标题】:SwiftUI Binding not triggering when changed from within a button action 【发布时间】:2021-01-05 00:56:11 【问题描述】:我构建了一个具有以下附加功能的自定义文本字段:
-
可以是安全的/不安全的
有一个显示/隐藏密码按钮(如果安全)
有一个明文按钮
下面有一个错误文本标签。
输入文本时,占位符在文本字段上方动画(类似于 android 材料文本字段)
有一个 onChange 闭包,您可以在其中运行验证
按钮操作将我的文本绑定设置为“”
当我在文本字段中输入文本时,绑定会正确触发。
但是当我点击按钮清除文本时,绑定不会触发。
这是有原因的吗?
这是我的结构。请参阅 cmets 了解问题所在。
public struct CustomTextField: View
// MARK: - Property Wrappers
@Environment(\.isEnabled) private var isEnabled: Bool
@Binding private var text: String
@Binding private var errorText: String
@State private var secureTextHidden: Bool = true
// MARK: - Struct Properties
private var placeholder: String
private var isSecure: Bool
private var hasLabel: Bool = false
private var hasErrorLabel: Bool = false
private var onChange: ((String) -> Void)?
// MARK: - Computed Properties
private var borderColor: Color
guard isEnabled else
return .gray
if !errorText.isEmpty
return .red
else if !text.isEmpty
return .blue
else
return .gray
private var textColor: Color
guard isEnabled else
return Color.gray.opacity(0.5)
return .black
private var textField: some View
let binding = Binding<String>
self.text
set:
// This is triggered correctly when text changes, but not when text is changed within my button action.
self.text = $0
onChange?($0)
if isSecure && secureTextHidden
return SecureField(placeholder, text: binding)
.eraseToAnyView()
else
return TextField(placeholder, text: binding)
.eraseToAnyView()
private var hasText: Bool !text.isEmpty
private var hasError: Bool !errorText.isEmpty
// MARK: - Init
/// Initializes a new CustomTextField
/// - Parameters:
/// - placeholder: the textfield placeholder
/// - isSecure: if true, textfield will behave like a password field
/// - hasLabel: Show placeholder as a label when text is entered
/// - hasErrorLabel: Visible Error Label underneath
/// - onChange: code that will run on each keystroke (optional)
public init(
placeholder: String,
text: Binding<String>,
errorText: Binding<String> = .constant(""),
isSecure: Bool = false,
hasLabel: Bool = false,
hasErrorLabel: Bool = false,
onChange: ((String) -> Void)? = nil
)
self.placeholder = placeholder
_text = text
_errorText = errorText
self.isSecure = isSecure
self.hasLabel = hasLabel
self.hasErrorLabel = hasErrorLabel
self.onChange = onChange
// MARK: - Body
public var body: some View
VStack(alignment: .leading, spacing: .textMargin)
if hasLabel
Text("\(placeholder)")
.foregroundColor(textColor)
.offset(
x: hasText ? 0 : 16,
y: hasText ? 0 : 30
)
.opacity(hasText ? 1 : 0)
.animation(.easeOut(duration: 0.3))
else
EmptyView()
HStack(alignment: .center, spacing: 8)
ZStack(alignment: Alignment(horizontal: .trailing, vertical: .center))
HStack(alignment: .center, spacing: 16)
textField
HStack(alignment: .center, spacing: 16)
if hasText && isEnabled
Button
text = ""
// I had to trigger onChange manually as setting my text above is not triggering my binding block.
onChange?(text)
label:
Image(systemName: "xmark.circle.fill")
.buttonStyle(BorderlessButtonStyle())
.foregroundColor(.black)
if isSecure && isEnabled
Button
secureTextHidden.toggle()
label:
Image(systemName: secureTextHidden ? "eye.fill" : "eye.slash.fill")
.buttonStyle(BorderlessButtonStyle())
.foregroundColor(Color.gray.opacity(0.5))
.padding(.margin)
.frame(height: .textFieldHeight, alignment: .center)
.background(
ZStack
RoundedRectangle(cornerRadius: 4)
.fill(.white)
RoundedRectangle(cornerRadius: 4)
.strokeBorder(borderColor, lineWidth: 2)
)
if hasErrorLabel && isEnabled
Text(errorText)
.lineLimit(2)
.font(.caption)
.foregroundColor(.red)
.offset(y: hasError ? 0 : -.textFieldHeight)
.opacity(hasError ? 1 : 0)
.animation(.easeOut(duration: 0.3))
else
EmptyView()
.foregroundColor(textColor)
.onAppear
if hasText
onChange?(text)
【问题讨论】:
与 Xcode 12.1 / ios 14.1 配合使用,只需简单的复制和绑定到字符串状态属性。 @Asperi 您在按钮操作中注释掉了 onChange 吗?它应该触发绑定集块内的 onChange 。我必须将它添加到按钮操作中,因为当我将文本设置为“”时它不会触发我的绑定 您是否在按钮操作中注释掉了 onChange? - 是的,只使用了text = ""
。
@Asperi 真的很奇怪......它对我不起作用......
【参考方案1】:
更改text
绑定不会影响传递给文本字段的binding
绑定。您应该正确了解这两个绑定之间的关系。 set:
块仅在 binding
已被文本字段更改时触发。
如果您的最低目标 iOS 是 14,您还可以使用 onChange
修饰符而不是您的处理程序来观察 text
绑定的任何变化,以便在绑定发生变化时调用您的 onChange
。而且,您不必为文本字段进行binding
绑定。直接通过text
绑定即可。
【讨论】:
不幸的是,我的最低目标是 13 当@Binding 值发生变化时,还有其他方法可以触发某些事情吗?除了设置的代码块?以上是关于从按钮操作中更改时未触发 SwiftUI 绑定的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI:“@Published”属性更改时未刷新“带有组的动态列表”