我想实现在SecureField中显示和隐藏密码的功能。以下是SecureField的代码我添加了一个按钮,它将被检测到在SecureField中显示和隐藏文本。但是swiftUI没有类似于isSecureTextEntry的功能是否有其他方法可以在Textfield和SecureField之间切换
SecureField(placeholder, text: $textValue, onCommit: {
onReturn?()
})
.onTapGesture {
onBegin?()
}
.keyboardType(keyboardType)
.font(Font.label.bodyDefault)
.disableAutocorrection(true)
.frame(maxWidth: .infinity, maxHeight: 40)
.padding(.leading)
这并不像它应该的那样简单。其中一个答案很接近,但它并没有完全涵盖所有内容,因为它对@FocusState
的实现很差。iOS 15无疑让这个领域更容易构建,因为有两个关键:1。使用.opacity()
来显示和隐藏字段,2。使用CCD_ 3在两个字段之间进行无缝转换。这种方法唯一的缺点是,为了使转换无缝,必须将键盘限制为仅ASCII兼容字符,因此某些语言被排除在外,如俄语。
struct CustomSecureField: View {
@FocusState var focused: focusedField?
@State var showPassword: Bool = false
@Binding var password: String
var body: some View {
HStack {
ZStack(alignment: .trailing) {
TextField("Password", text: $password)
.focused($focused, equals: .unSecure)
.autocapitalization(.none)
.disableAutocorrection(true)
// This is needed to remove suggestion bar, otherwise swapping between
// fields will change keyboard height and be distracting to user.
.keyboardType(.alphabet)
.opacity(showPassword ? 1 : 0)
SecureField("Password", text: $password)
.focused($focused, equals: .secure)
.autocapitalization(.none)
.disableAutocorrection(true)
.opacity(showPassword ? 0 : 1)
Button(action: {
showPassword.toggle()
focused = focused == .secure ? .unSecure : .secure
}, label: {
Image(systemName: self.showPassword ? "eye.slash.fill" : "eye.fill")
.padding()
})
}
}
}
// Using the enum makes the code clear as to what field is focused.
enum focusedField {
case secure, unSecure
}
}
接受的答案(Yrb答案(工作不正常,当将眼睛切换到可见通行证并输入一些密码,然后再次切换回安全模式并输入一些字符时,该字段将为空,所有输入的字符都将消失。
这是我的解决方案,对iOS 15没有任何依赖:
struct CustomSecureField: View {
@State var isPasswordVisible: Bool = false
@Binding var password: String
var placeholder = ""
var body: some View {
HStack(spacing: 12) {
ZStack {
if password.isEmpty {
HStack {
Text(placeholder)
Spacer()
}
}
ZStack {
TextField("",
text: $password)
.frame(maxHeight: .infinity)
.opacity(isPasswordVisible ? 1 : 0)
SecureField("",
text: $password)
.frame(maxHeight: .infinity)
.opacity(isPasswordVisible ? 0 : 1)
}
}
.padding(.horizontal, 8)
Button {
isPasswordVisible.toggle()
} label: {
Image(systemName: isPasswordVisible ? "eye.slash.fill" : "eye.fill")
}
.padding(.trailing, 8)
}
.frame(height: 44)
.frame(maxWidth: .infinity)
.background(Color.gray.opacity(0.4))
.cornerRadius(5)
.padding(.horizontal)
}
}
其他答案中提出的在TextField和SecureField之间切换有一些缺点,主要是:
- 在状态之间切换时键盘将关闭
- 在状态之间切换后开始键入时,该字段将清除
我使用UITextField和UIViewRepresentable协议创建了自己的TextField,并用它解决了这两个问题
import SwiftUI
struct TextField_port: UIViewRepresentable {
// MARK: Lifecycle
init(text: Binding<String>, placeholder: String = "", isSecureEntry: Bool = false) {
_text = text
self.isSecureEntry = isSecureEntry
self.placeholder = placeholder
}
// MARK: Internal
typealias UIViewType = UITextField
class Coordinator: NSObject, UITextFieldDelegate {
// MARK: Lifecycle
init(_ parent: TextField_port, text: Binding<String>) {
self.parent = parent
_text = text
}
// MARK: Internal
let parent: TextField_port
@Binding var text: String
var updateCharacterTask: Task<Void, Never>?
// MARK: - UITextFieldDelegate
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
updateCharacterTask?.cancel()
if let textRange = Range(range, in: text) {
let updatedText = text.replacingCharacters(in: textRange,
with: string)
text = updatedText
}
return false
}
func textFieldShouldClear(_ textField: UITextField) -> Bool {
updateCharacterTask?.cancel()
text = ""
return false
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
@Binding var text: String
let placeholder: String
let isSecureEntry: Bool
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
setText(textField: textField, text: text, coordinator: context.coordinator)
textField.delegate = context.coordinator
textField.placeholder = placeholder
textField.returnKeyType = .done
textField.clearButtonMode = .whileEditing
textField.autocapitalizationType = .none
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.placeholder = placeholder
setText(textField: uiView, text: text, coordinator: context.coordinator)
}
private func setText(textField: UITextField, text: String, coordinator: Coordinator) {
coordinator.updateCharacterTask?.cancel()
if isSecureEntry {
let securedText = text.map { _ in "●" }.joined()
// If we are adding a new character, reveal last for a few seconds
if (textField.text?.count ?? 0) < text.count {
var lastRevealed = securedText
lastRevealed.removeLast()
textField.text = lastRevealed + text.suffix(1)
// Hide last character after a delay
coordinator.updateCharacterTask = Task { [securedText] in
try? await Task.sleep(seconds: 0.4)
if !Task.isCancelled {
await MainActor.run {
textField.text = securedText
}
}
}
} else {
// If we are deleting characters, just show the secured text
textField.text = securedText
}
} else {
textField.text = text
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self, text: $text)
}
}
struct TextField_port_Previews: PreviewProvider {
@State static var text: String = ""
static var previews: some View {
TextField_port(text: $text, placeholder: "test")
}
}