当从按钮操作中更改时,SwiftUI绑定不会触发
我构建了一个具有以下附加功能的自定义文本字段:当从按钮操作中更改时,SwiftUI绑定不会触发,swift,swiftui,Swift,Swiftui,我构建了一个具有以下附加功能的自定义文本字段: 可能是安全的/不安全的 具有显示/隐藏密码按钮(如果安全) 有一个明文按钮 下面有一个错误文本标签 输入文本时,占位符将在文本字段上方设置动画(类似于android材质文本字段) 有一个onChange闭包,您可以在其中运行验证 按钮操作将我的文本绑定设置为“” 当我在文本字段中输入文本时,绑定会正确触发 但当我点击按钮清除文本时,绑定不会触发 这有什么原因吗 这是我的结构。有关问题发生的位置,请参见注释 public struct CustomT
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)
}
}
}
}
公共结构CustomTextField:视图{
//标记:-属性包装器
@环境(\.isEnabled)私有变量isEnabled:Bool
@绑定私有变量文本:字符串
@绑定私有变量errorText:字符串
@状态私有变量secureTextHidden:Bool=true
//标记:-结构属性
私有变量占位符:字符串
私有变量安全:Bool
私有变量hasLabel:Bool=false
私有变量hasErrorLabel:Bool=false
私有变量onChange:((字符串)->Void)?
//标记:-计算属性
私有颜色:颜色{
我找到了其他人{
返回,格雷
}
如果!errorText.isEmpty{
返回,红色
}否则,如果!text.isEmpty{
返回,蓝色
}否则{
返回,格雷
}
}
私有变量textColor:Color{
我找到了其他人{
返回颜色。灰色。不透明度(0.5)
}
返回,黑色
}
私有变量textField:一些视图{
让绑定=绑定{
自我文本
}设置:{
//当文本更改时会正确触发,但在“我的按钮”操作中更改文本时不会正确触发。
self.text=$0
一次更改?($0)
}
如果isSecure&&secureTextHidden{
返回SecureField(占位符,文本:绑定)
.eraseToAnyView()
}否则{
返回文本字段(占位符,文本:绑定)
.eraseToAnyView()
}
}
私有变量hasText:Bool{!text.isEmpty}
私有变量hasError:Bool{!errorText.isEmpty}
//MARK:-Init
///初始化新的CustomTextField
///-参数:
///-占位符:文本字段占位符
///-IsSecurity:如果为true,textfield的行为将类似于密码字段
///-hasLabel:输入文本时将占位符显示为标签
///-hasErrorLabel:下方可见的错误标签
///-onChange:每次击键时运行的代码(可选)
公共初始化(
占位符:字符串,
案文:有约束力,
errorText:Binding=.constant(“”),
IsSecurity:Bool=false,
hasLabel:Bool=false,
hasErrorLabel:Bool=false,
onChange:((字符串)->Void)?=nil
) {
self.placeholder=占位符
_文本=文本
_errorText=errorText
self.isSecure=isSecure
self.hasLabel=hasLabel
self.hasErrorLabel=hasErrorLabel
self.onChange=onChange
}
//马克:身体
公共机构:一些看法{
VStack(对齐:。前导,间距:。文本边距){
如果有标签{
文本(“\(占位符)”)
.foregroundColor(文本颜色)
.抵消(
x:hasText?0:16,
y:hasText?0:30
)
.不透明度(hasText?1:0)
.animation(.easeOut(持续时间:0.3))
}否则{
EmptyView()
}
HStack(对齐:。中心,间距:8){
ZStack(对齐:对齐(水平:尾随,垂直:中心)){
HStack(对齐:中心,间距:16){
文本字段
HStack(对齐:中心,间距:16){
如果已启用hasText&&I{
钮扣{
text=“”
//我必须手动触发onChange,因为上面设置的文本不会触发绑定块。
onChange?(文本)
}标签:{
图像(系统名称:“xmark.circle.fill”)
}
.buttonStyle(无边框buttonStyle())
.foregroundColor(.黑色)
}
如果是安全的,则启用(&I){
钮扣{
secureTextHidden.toggle()
}标签:{
图像(系统名称:secureTextHidden?“eye.fill”:“eye.slash.fill”)
}
.buttonStyle(无边框buttonStyle())
.前底色(颜色.灰色.不透明度(0.5))
}
}
}
}
.padding(.margin)
.frame(高度:.textFieldHeight,对齐:.center)
.背景(
ZStack{
圆角转角(拐角半径:4)
.fill(.白色)
圆角转角(拐角半径:4)
.strokeBorder(边框颜色,线宽:2)
}
)
}
如果已启用hasErrorLabel&&I{
文本(错误文本)
.lineLimit(2)
.font(.caption)
F