【发布时间】: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 真的很奇怪......它对我不起作用......