This is not as straightforward as it should be. One of the answers there is pretty close, but it doesn't quite cover everything as it has a poor implementation of @FocusState
. iOS 15 definitely made this field easier to construct, as there are two keys: 1. using .opacity()
to show and hide the fields, 2. using @FocusState
to make a seamless transition between the two fields. The only downside to this method is in order to make the transition seamless, you must restrict the keyboard to only ASCII-compatible characters, so some languages are left out, such as Russian.
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
}
}