Greetings, traveler!
SwiftUI provides a clean and declarative way to build user interfaces, but it doesn’t always include built-in solutions for every use case. One such limitation is the lack of a native way to toggle the secure input state of a text field, such as switching between a standard TextField
and a SecureField
for password inputs. In UIKit, you can simply use the isSecureTextEntry
property on a UITextField
to handle this. This is a common requirement for login screens where users might want to reveal or hide their password. In this article, we’ll explore a practical approach to address this by creating a custom SecureTextField
view that seamlessly switches between secure and non-secure input modes.
The Challenge
In SwiftUI, TextField
and SecureField
are distinct views with different behaviors. TextField
displays text as-is, while SecureField
obscures it for sensitive data like passwords. There’s no built-in mechanism to toggle a single field between these states dynamically. To achieve this, we can create a custom view that manages both fields and switches their visibility based on a binding.
The Solution: A Custom SecureTextField
The SecureTextField
view presented here encapsulates both a TextField
and a SecureField
, using a binding to control which one is active. It ensures a smooth user experience by maintaining focus and synchronizing the text input across both fields. Below is the implementation, followed by a breakdown of how it works.
import SwiftUI
public struct SecureTextField: View {
private enum Field: Hashable {
case regular, secure
}
@Binding private var text: String
private let isSecureTextEntry: Bool
@FocusState private var focusedField: Field?
private let title: LocalizedStringKey
private var textContentType: UITextContentType = .password
public init(
_ title: LocalizedStringKey,
text: Binding<String>,
isSecure: Bool
) {
self.title = title
_text = text
isSecureTextEntry = isSecure
}
public var body: some View {
Group {
if isSecureTextEntry {
secureField()
} else {
textField()
}
}
.onChange(of: isSecureTextEntry) { _, newValue in
focusedField = isSecureTextEntry ? .secure : .regular
}
}
}
private extension SecureTextField {
func textField() -> some View {
TextField(title, text: $text)
.focused($focusedField, equals: .regular)
.autocorrectionDisabled()
.textContentType(textContentType)
}
func secureField() -> some View {
SecureField(title, text: $text)
.focused($focusedField, equals: .secure)
.textContentType(textContentType)
}
}
public extension SecureTextField {
func setTextContentType( _ textContentType: UITextContentType) -> some View {
var view = self
view.textContentType = textContentType
return view
}
}
How It Works
The SecureTextField
view is designed to be reusable and straightforward. Here’s a step-by-step explanation of its components and logic:
- State and Bindings:
@Binding var text: String
: This binds to the text input, shared between both fields to ensure the entered text persists when switching modes.let isSecureTextEntry: Bool
: This controls whether theTextField
(false
) orSecureField
(true
) is active.@FocusState private var focusedField: Field?
: A private state to manage which field has focus, using an enum (Field
) to differentiate between the regular and secure states.
- View Composition:
- The
textField()
andsecureField()
helper methods define theTextField
andSecureField
, respectively. Each is bound to the same text binding and uses the focused modifier to manage focus state.
- The
- Focus Management:
- The
.onChange(of: isSecureTextEntry)
modifier updates thefocusedField
state wheneverisSecureTextEntry
changes, ensuring the correct field gains focus. This prevents the keyboard from dismissing during the switch, maintaining a smooth user experience.
- The
- Additional Configurations:
autocorrectionDisabled()
is applied to the TextField to avoid unwanted autocorrect behavior, especially for passwords.- The
textContentType
is set on both fields view builder method to ensure appropriate keyboard types and autofill behavior.
Example Usage
To demonstrate how SecureTextField
can be used, here’s a sample implementation with a toggle button:
struct ContentView: View {
@State private var text: String = ""
@State private var isSecure: Bool = false
var body: some View {
ScrollView {
VStack {
SecureTextField(
"Password",
text: $text,
isSecureTextEntry: $isSecure
)
.padding()
Button(isSecure ? "Show Password" : "Hide Password") {
isSecure.toggle()
}
}
Spacer()
}
}
}
In this example, the SecureTextField
is paired with a button that toggles isSecure
. When the button is pressed, the view switches between secure and non-secure input, updating the visibility and focus of the fields while preserving the entered text.
Why This Approach?
This solution is lightweight and reusable, requiring minimal code to integrate into any SwiftUI project. By using a ZStack
and opacity
to switch between fields, it avoids complex state management or view recreation. The focus handling ensures the keyboard remains active during the switch, which is critical for usability. Additionally, the view supports localization and text content types, making it versatile for various input scenarios.
Conclusion
While SwiftUI doesn’t natively support toggling secure input for text fields, creating a custom view offers a clean and effective solution. By managing two fields with shared state and focus, this component provides a seamless user experience for password visibility toggling.
If you enjoyed this article, please feel free to follow me on my social media: