39

I've looked through the forums but I'm seeing mixed answers especially ones from an old Xcode version.

I only decided to add this after already typing up the code I have in this: Photo

How could I go about doing that? I was wanting the 'Eyeball' toggle implemented on the password field.

Cristik
  • 30,989
  • 25
  • 91
  • 127
Moe Kurdi
  • 401
  • 1
  • 4
  • 8

14 Answers14

52

enter image description hereYou can simply use this view instead of SecureField. It has the eye icon inside, so for most cases you don't need to care about anything.

struct SecureInputView: View {
    
    @Binding private var text: String
    @State private var isSecured: Bool = true
    private var title: String
    
    init(_ title: String, text: Binding<String>) {
        self.title = title
        self._text = text
    }
    
    var body: some View {
        ZStack(alignment: .trailing) {
            Group {
                if isSecured {
                    SecureField(title, text: $text)
                } else {
                    TextField(title, text: $text)
                }
            }.padding(.trailing, 32)

            Button(action: {
                isSecured.toggle()
            }) {
                Image(systemName: self.isSecured ? "eye.slash" : "eye")
                    .accentColor(.gray)
            }
        }
    }
}

Copy paste this view into your app, and instead of SecureField just use SecureInputView.

Example: SecureInputView("Password", text: $viewModel.password)

Vahagn Gevorgyan
  • 2,635
  • 19
  • 25
  • 9
    There are 3 issues with this solution. 1. the secureField is cleared if you enter text, tap on show password, tap on hide password, and enter text again. 2. when tapping on show password the keyboard is dismissed. 3. Using a ZStack will have text be written under the button. – Mihai Georgescu Oct 21 '21 at 11:15
  • 1
    @MihaiGeorgescu did you find out how to use it without this problems? – mmm Mar 30 '22 at 08:54
17

For those still looking for a simple solution to this issue (requires iOS 15 for swiftUI 3):

With the new @FocusState introduced in swiftUI 3, it's possible to keep focus and keyboard open while changing State.

By using the opacity modifier instead of conditionally changing between SecureField and TextField, the focus can jump between the two without issues with the keyboard.

This allows you to toggle between revealing and hiding the password with the the eye button included in the ZStack.

import SwiftUI

struct SecureTextFieldWithReveal: View {

@FocusState var focus1: Bool
@FocusState var focus2: Bool
@State var showPassword: Bool = false
@State var text: String = ""

var body: some View {
    HStack {
        ZStack(alignment: .trailing) {
            TextField("Password", text: $text)
                .modifier(LoginModifier())
                .textContentType(.password)
                .focused($focus1)
                .opacity(showPassword ? 1 : 0)
            SecureField("Password", text: $text)
                .modifier(LoginModifier())
                .textContentType(.password)
                .focused($focus2)
                .opacity(showPassword ? 0 : 1)
            Button(action: {
                showPassword.toggle()
                if showPassword { focus1 = true } else { focus2 = true }
            }, label: {
                Image(systemName: self.showPassword ? "eye.slash.fill" : "eye.fill").font(.system(size: 16, weight: .regular))
                    .padding()
            })
        }
    }
}
}

Password field hidden

Password field revealed

This is the code in LoginModifier:

import SwiftUI

struct LoginModifier: ViewModifier {

var borderColor: Color = Color.gray

func body(content: Content) -> some View {
    content
        .disableAutocorrection(true)
        .autocapitalization(.none)
        .padding()
        .overlay(RoundedRectangle(cornerRadius: 10).stroke(borderColor, lineWidth: 1))
}
}

The only issue I've had with this method is that on regaining focus SecureField will automatically clear any text already entered if you start typing. This seems to be a design choice by Apple.

Oscar Nowell
  • 171
  • 1
  • 4
14

The possible approach is to show either TextField or SecureField joined to one storage, like in below demo:

Updated: Xcode 13.4 / iOS 15.5

with FocusState, now it is possible to change fields without having the keyboard disappear

demo2

Main part:

if showPassword {
    TextField("Placeholer", text: $password)
        .focused($inFocus, equals: .plain)
} else {
    SecureField("Placeholder", text: $password)
        .focused($inFocus, equals: .secure)
}
Button("toggle") {
    self.showPassword.toggle()
    inFocus = showPassword ? .plain : .secure
}

Test module in project is here

Old:

struct DemoShowPassword: View {
    @State private var showPassword: Bool = false
    @State private var password = "demo"

    var body: some View {
        VStack {
            if showPassword {
                TextField("Placeholer", text: $password)
            } else {
                SecureField("Placeholder", text: $password)
            }
            Button("toggle") {
                self.showPassword.toggle()
            }
        }
    }
}
Troy
  • 5,319
  • 1
  • 35
  • 41
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • I'm still a complete noob. Is this added to the code as is? (assuming not) – Moe Kurdi Jul 26 '20 at 04:39
  • 3
    there's a problem with this approach. If the keyboard is shown and you press the toggle button, it will dismiss the keyboard, as now a different Field gets shown – andrei Mar 05 '21 at 08:01
  • Another problem is TextField allows third-party keyboards to access that field, while SecureField does not. IMO makes sensitive data handled by SecureField insecure and threatens user's privacy. See my answer below for details. – Mr. 0xCa7 Oct 31 '22 at 10:01
4

I am using this approach for now in my current application. I would like to say that it works flawlessly.

@ViewBuilder
    func secureField() -> some View {
        if self.showPassword {
            TextField("Password", text: $passwordText)
                .font(.system(size: 15, weight: .regular, design: .default))
                .keyboardType(.default)
                .autocapitalization(.none)
                .disableAutocorrection(true)
                .frame(maxWidth: UIScreen.main.bounds.width, maxHeight: 60, alignment: .center)
        } else {
            SecureField("Password", text: $passwordText)
                .font(.system(size: 15, weight: .regular, design: .default))
                .keyboardType(.default)
                .autocapitalization(.none)
                .disableAutocorrection(true)
                .frame(maxWidth: UIScreen.main.bounds.width, maxHeight: 60, alignment: .center)
        }
    }

Use:

HStack{
    Image(systemName: "lock.fill")
        .foregroundColor(passwordText.isEmpty ? .secondary : .primary)
        .font(.system(size: 18, weight: .medium, design: .default))
        .frame(width: 18, height: 18, alignment: .center)
    secureField()
    if !passwordText.isEmpty {
        Button(action: {
            self.showPassword.toggle()
        }, label: {
            ZStack(alignment: .trailing){
                Color.clear
                    .frame(maxWidth: 29, maxHeight: 60, alignment: .center)
                Image(systemName: self.showPassword ? "eye.slash.fill" : "eye.fill")
                    .font(.system(size: 18, weight: .medium))
                    .foregroundColor(Color.init(red: 160.0/255.0, green: 160.0/255.0, blue: 160.0/255.0))
            }
        })
    }
}
.padding(.horizontal, 15)
.background(Color.primary.opacity(0.05).cornerRadius(10))
.padding(.horizontal, 15)
Kerem Cesme
  • 122
  • 2
  • 9
  • This is the best answer IMO! – blrsk Feb 09 '22 at 16:29
  • 1
    The only change I would suggest to make things even simpler is to use .overlay() instead of the ZStack for the toggle. It'd look something like this: .overlay( Image(systemName: showPassword ? "eye.slash.fill" : "eye.fill") .font(.system(size: 15, weight: .medium)) .foregroundColor(Color.theme.lightGray) .padding() .onTapGesture { showPassword.toggle() } , alignment: .trailing ) – blrsk Feb 09 '22 at 16:51
  • This plus @blrsk's addition is very good as it shows the eye icon inside the password field but there's an issue that it clashes with the text if the input text is very long. – BadmintonCat Mar 11 '22 at 08:59
  • You can add .padding(.trailing) to your textfield to remedy that – blrsk Mar 11 '22 at 14:42
4

I am afraid most answers here fail to mention that switching from SecureField to TextField reduces security. SecureField is essentially, per Apple documentation, simply a TextField where user input is masked [1]. However, SecureField also does one other job - it prevents using third-party keyboards (keyboard extensions) and thus protects user's security and privacy.

Ideal solution would be to have input field that is both "secure" and has mask()/unmask() methods. Unfortunately, the only advice I found is when you want to implement unmasking as other answers suggested, at least block third-party keyboards from your application entirely [2]:

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplication.ExtensionPointIdentifier) -> Bool {
        return extensionPointIdentifier != UIApplication.ExtensionPointIdentifier.keyboard
    }
}
@main
struct MyApplication: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Should also mention that UIApplicationDelegate is part of UIKit, not SwiftUI. There is no "native" SwiftUI for the same purpose as for now, although the above works fine for now.

  1. https://developer.apple.com/documentation/swiftui/securefield
  2. https://www.securing.pl/en/third-party-iphone-keyboards-vs-your-ios-application-security/
Mr. 0xCa7
  • 100
  • 7
3

For those that do not want the keyboard disappearing while typing:

struct CustomSecureField: View {
    @State var password: String = ""
    @State var isShowingPassword: Bool = false
    var body: some View {
        VStack{
            ZStack{
                HStack{
                    SecureField(
                        isShowingPassword ? "" : "Password",
                        text: $password) {
                        
                    }.opacity(isShowingPassword ? 0 : 1)
                    // show only one of these is not empty.
                    if(!password.isEmpty){
                        Image(systemName: isShowingPassword ? "eye.slash" : "eye")
                            .foregroundColor(.white)
                            .frame(width: 20, height: 20, alignment: .center)
                            .modifier(TouchDownUpEventModifier(changeState: { (buttonState) in
                                if buttonState == .pressed {
                                    isShowingPassword = true
                                } else {
                                    isShowingPassword = false
                                }
                            }))
                    }
                }
                if(isShowingPassword){
                    HStack{
                        Text(password)
                            .foregroundColor(.white)
                            .allowsHitTesting(false)
                        Spacer()
                    }
                }
            }
        }.padding(10)
        .background(Color.gray)
    }
}

and the on tap and release modifier:

public enum ButtonState {
    case pressed
    case notPressed
}

/// ViewModifier allows us to get a view, then modify it and return it
public struct TouchDownUpEventModifier: ViewModifier {
    
    /// Properties marked with `@GestureState` automatically resets when the gesture ends/is cancelled
    /// for example, once the finger lifts up, this will reset to false
    /// this functionality is handled inside the `.updating` modifier
    @GestureState private var isPressed = false
    
    /// this is the closure that will get passed around.
    /// we will update the ButtonState every time your finger touches down or up.
    let changeState: (ButtonState) -> Void
    
    /// a required function for ViewModifier.
    /// content is the body content of the caller view
    public func body(content: Content) -> some View {
        
        /// declare the drag gesture
        let drag = DragGesture(minimumDistance: 0)
            
            /// this is called whenever the gesture is happening
            /// because we do this on a `DragGesture`, this is called when the finger is down
            .updating($isPressed) { (value, gestureState, transaction) in
                
            /// setting the gestureState will automatically set `$isPressed`
            gestureState = true
        }
        
        return content
        .gesture(drag) /// add the gesture
        .onChange(of: isPressed, perform: { (pressed) in /// call `changeState` whenever the state changes
            /// `onChange` is available in iOS 14 and higher.
            if pressed {
                self.changeState(.pressed)
            } else {
                self.changeState(.notPressed)
            }
        })
    }
    
    /// if you're on iPad Swift Playgrounds and you put all of this code in a seperate file,
    /// you need to add a public init so that the compiler detects it.
    public init(changeState: @escaping (ButtonState) -> Void) {
        self.changeState = changeState
    }
}

From what I have seen there is no easy way to keep the text showing unless you want to lose focus on your text.

Cheers!

Derwrecked
  • 771
  • 1
  • 6
  • 17
3

@Derwrecked's answer really gave me some good inspirations: instead using two TextField, change SecureField opacity and show/hide a Text can avoid keyboard dismissing problem, but in his answer that long TouchDownUpEventModifier seems unnecessarily complicated, you can easily achieve the same effect using a Button with label.

So below is my approach, and the previews look like this enter image description here

import SwiftUI

struct SecureInput: View {
    let placeholder: String
    @State private var showText: Bool = false
    @State var text: String
    var onCommit: (()->Void)?
    
    var body: some View {
        
        HStack {
            ZStack {
                SecureField(placeholder, text: $text, onCommit: {
                    onCommit?()
                })
                .opacity(showText ? 0 : 1)
                
                if showText {
                    HStack {
                        Text(text)
                            .lineLimit(1)
                        
                        Spacer()
                    }
                }
            }
            
            Button(action: {
                showText.toggle()
            }, label: {
                Image(systemName: showText ? "eye.slash.fill" : "eye.fill")
            })
            .accentColor(.secondary)
        }
        .padding()
        .overlay(RoundedRectangle(cornerRadius: 12)
                    .stroke(Color.secondary, lineWidth: 1)
                    .foregroundColor(.clear))
    }
    
}

struct SecureInput_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            SecureInput(placeholder: "Any placeholder", text: "")
                .padding()
                .previewLayout(.fixed(width: 400, height: 100))
            
            SecureInput(placeholder: "Any placeholder", text: "")
                .padding()
                .preferredColorScheme(.dark)
                .previewLayout(.fixed(width: 400, height: 100))
        }
    }
}
  • An known issue for this approach: since when password is shown, SecureField has 0.0 opacity, so input cursor is not visible. But users can still keep typing without losing keyboard focus, so I find it acceptable, if anyone has a solution for this, please comment and share.
Daniel Hu
  • 423
  • 4
  • 11
2

I've been looking for a nice solution for my use-case. I had to have an indicator which field is in focus. Successfully done that with onEditingChanged from TextField, but SecureField doesn't provide that closure. I tried stacking them both and disabling the SecureField so it only shows 'hidden' characters. That resulted in cursor sticking to the TextField text while SecureField text had different text width which made it seem buggy. Imagine a password with a lot of I's in it. The idea is to have a main binding with two side bindings that update the main one and sync each other.

struct CustomSecureField : View {
    var label : String
    @Binding var text : String
    @State var isEditing = false
    @State var isHidden = true
    var body : some View {
        let showPasswordBinding = Binding<String> {
            self.text
        } set: {
            self.text = $0
        }
        let hidePasswordBinding = Binding<String> {
            String.init(repeating: "●", count: self.text.count)
        } set: { newValue in
            if(newValue.count < self.text.count) {
                self.text = ""
            } else {
                self.text.append(contentsOf: newValue.suffix(newValue.count - self.text.count) )
            }
        }

        return ZStack(alignment: .trailing) {
                TextField(
                    label,
                    text: isHidden ? hidePasswordBinding : showPasswordBinding,
                    onEditingChanged: { editingChanged in
                        isEditing = editingChanged
                    }
                )
                Image("eye").frame(width: 50, height: 50).onTapGesture {
                    isHidden.toggle()
                }
            }
        }
    }
}
Robert Sudec
  • 101
  • 6
  • This is a very nice workaround if you have to support < iOS 15 and can't use `@FocusState`. And this works perfectly if you have your usual user who removes and adds characters only at the end of string, not middle. BUT! SwiftUI's `SecureField` doesn't support that either. When user tries to remove any character - at beginning, middle or end - the whole field gets removed. So change `self.text = String(self.text.dropLast())` to `self.text = ""` and you get yourself nice clean version of `SecuredField` behavior without having to switch between fields, apply opacity or lose cursor. Nice job! – roxanneM Jul 04 '22 at 15:14
  • Nice catch! @roxanneM – Robert Sudec Jul 06 '22 at 13:25
2

Here is a solution that meets the following requirements.
Requires iOS 15+, tested on iOS 15.0 (simulator) and 16.4 (device).

  1. The keyboard stays open
  2. The save password to keychain (for autofill) action sheet doesn't show when switching modes
  3. The cursor shows in both modes
  4. The textfield doesn't clear when switching back to the secure mode. (it will clear if typing after switching to secure mode though)
  5. The field can be focused externally.
  6. If the field isn't focused then switching modes won't focus it
  7. As a bonus, if the app goes into the background it will switch to secure mode
struct PasswordField: View {

    let placeholder: String

    @Binding
    var text: String

    @State
    private var showText: Bool = false

    private enum Focus {
        case secure, text
    }

    @FocusState
    private var focus: Focus?

    @Environment(\.scenePhase)
    private var scenePhase

    var body: some View {
        HStack {
            ZStack {
                SecureField(placeholder, text: $text)
                    .focused($focus, equals: .secure)
                    .opacity(showText ? 0 : 1)
                TextField(placeholder, text: $text)
                    .focused($focus, equals: .text)
                    .opacity(showText ? 1 : 0)
            }

            Button(action: {
                showText.toggle()
            }) {
                Image(systemName: showText ? "eye.slash.fill" : "eye.fill")
            }
        }
        .onChange(of: focus) { newValue in
            // if the PasswordField is focused externally, then make sure the correct field is actually focused
            if newValue != nil {
                focus = showText ? .text : .secure
            }
        }
        .onChange(of: scenePhase) { newValue in
            if newValue != .active {
                showText = false
            }
        }
        .onChange(of: showText) { newValue in
            if focus != nil { // Prevents stealing focus to this field if another field is focused, or nothing is focused
                DispatchQueue.main.async { // Needed for general iOS 16 bug with focus
                    focus = newValue ? .text : .secure
                }
            }
        }
    }
}

It can be used like so, where the focus state for the form will work correctly.

struct LoginView: View {

    private enum Focus {
        case email, password
    }

    @FocusState
    private var focus: Focus?

    @State
    private var email: String = ""

    @State
    private var password: String = ""

    var body: some View {
        VStack {
            TextField("your@email.com", text: $email)
                .focused($focus, equals: .email)
            PasswordField(placeholder: "*****", text: $password)
                .focused($focus, equals: .password)
        }
        .onSubmit {
            if focus == .email {
                focus = .password
            } else if focus == .password {
                // do login
            }
        }
    }
}
Jonathan.
  • 53,997
  • 54
  • 186
  • 290
  • Is there any way to prevent clearing the textfield when switching back to secure mode ? – Shawn Frank Aug 05 '23 at 12:11
  • The code in my answer should do that. – Jonathan. Aug 09 '23 at 19:16
  • I tried this but it seems to clear. My scenario is I type some text in secure mode, I switch to regular mode and I can add more text. If I switch back to secure mode and start typing, it clears the textfield and starts again. – Shawn Frank Aug 11 '23 at 02:23
  • @ShawnFrank ah yeah I put that in my answer that if you start typing after switching it will clear. that is unavoidable as far as I've found, any password textfield will clear if it loses and then gain focus its just a system behavior. – Jonathan. Aug 30 '23 at 10:45
1

@Vahagn Gevorgyan's answer was almost correct but some people were struggling with maintaining state... this is because the field is using a binding which should ideally be held in a parent view. Therefore just update the bindings to state variables like this

struct SecureInputView: View {
    
    let placeholder: String
    
    @State var text: String
    @State var isSecure: Bool = true
    
    
    var body: some View {
        ZStack(alignment: .trailing) {
            Group {
                if isSecure {
                    SecureField(placeholder, text: $text)
                } else {
                    TextField(placeholder, text: $text)
                }
            }.padding(.trailing, 32)
            Button {
                isSecure.toggle()
            } label: {
                Image(systemName: isSecure ? "lock.fill" : "lock.open")
            }
        }
    }
}

1
@State private var isPasswordVisible = false

ZStack {
    TextField("", text: $password)
          .opacity(isPasswordVisible ? 1 : 0)

    SecureField("", text: $password)
           .opacity(isPasswordVisible ? 0 : 1)
}
  1. It doesn't need @Focus from iOS 15
  2. Keyboard will not disappear/appear on changing isPasswordVisible
  3. Password will not cleared on changing from visible to invisible then typing

Good Luck

Vahid
  • 3,352
  • 2
  • 34
  • 42
0

Crazy (AKA don't use in production) and very breakable solution here (but working at the time of writing):

extension TextField {
    
    public func secure(_ secure: Bool = true) -> TextField {
        if secure {
            var secureField = self
            withUnsafeMutablePointer(to: &secureField) { pointer in
                let offset = 32
                let valuePointer = UnsafeMutableRawPointer(mutating: pointer)
                    .assumingMemoryBound(to: Bool.self)
                    .advanced(by: offset)
                valuePointer.pointee = true
            }
            return secureField
        } else {
            return self
        }
    }
}

Usage

@State securing = true

...

TextField(...)
    .secure(securing)
Lorenzo Fiamingo
  • 3,251
  • 2
  • 17
  • 35
0

I made a custom text field that combine SecureField and TextField. This is an example where I used my custom field for both email and pwd.

This is my solution:

struct CustomTextField: View {
    let imageName: String
    let placeholderText: String
    
    var isSecureInput: Bool = false ///< define if this text field is secured and require eye button
    
    @State private var isSecured: Bool
    
    @Binding var text: String
    
    init(image: String,
         placeholder: String,
         text: Binding<String>,
         isSecureInput: Bool) {
        imageName = image
        placeholderText = placeholder
        self._text = text
        self.isSecureInput = isSecureInput
        isSecured = isSecureInput
        
    }
    
    var body: some View {
        VStack {
            HStack {
                Image(systemName: imageName)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 25, height: 25)
                    .foregroundColor(Color(.darkGray))
                
                if isSecureInput {
                    Group {
                        if isSecured {
                            SecureField(placeholderText, text: $text)
                        }
                        else {
                            TextField(text, text: $text)
                        }
                    }
                    .disableAutocorrection(true)
                    .autocapitalization(.none)
                    .textContentType(.password)
                    
                    
                    Button(action: {
                        isSecured.toggle()
                    }) {
                        Image(systemName: self.isSecured ? "eye.slash" : "eye")
                            .resizable()
                            .scaledToFit()
                            .frame(width: 25, height: 25)
                            .foregroundColor(Color(.darkGray))
                    }
                }
                
                else {
                    TextField(placeholderText, text: $text)
                }
            }
            
            Divider()
        }
    }
}
lerpof
  • 1
0

I put everything into a TextFieldStyle

struct AppTextFieldStyle: TextFieldStyle {
    @Environment(\.colorScheme) var colorScheme
    var label: String = ""
    @Binding var fieldValue: String
    var placeholderText: String = ""
    var systemImage: String?
    var isPassword: Bool = false
    @State var showPassword = false
    @Binding var hasError: Bool
    @Binding var validationMessage: String
    
    func _body(configuration: TextField<Self._Label>) -> some View {
        VStack(spacing: 5) {
            HStack {
                Text(label)
                .foregroundColor(.black)
                Spacer()
            }
            HStack {
                HStack(alignment: .bottom, spacing: 0) {
                    if let systemImage {
                        Image(systemName: systemImage)
                            .font(.headline)
                            .foregroundColor(
                                Color.gray.opacity(0.7))
                            .padding(.trailing, 10)
                    }
                    HStack {
                        if isPassword && showPassword {
                            TextField(placeholderText, text: self.$fieldValue)
                        } else {
                            configuration
                        }
                    }
                    .frame(minHeight: 23) // for some reason the textfield is not same height as secure textfield
                    if isPassword {
                        Button(
                            action: {
                                showPassword.toggle()
                            },
                            label: {
                                Image(systemName: self.showPassword ? "eye.slash" : "eye")
                                    .accentColor(.gray)
                            }
                        )
                        .font(.headline)
                        .foregroundColor(hasError ? .red : .gray.opacity(0.70))
                        .padding(.leading, 10)
                    }
                }
                .padding(.all, 10)
                .background(colorScheme == .dark ?
                            LinearGradient(
                                gradient: AppGradients.DarkTextFieldGradient.getGradient(),
                                startPoint: .top,
                                endPoint: .bottom
                            ) : nil)
                .background(colorScheme != .dark ? Color.white : nil)
            }
            .cornerRadius(10)
            // .shadow(color: .black.opacity(0.40), radius: 2, x: 1, y: 1)
            .overlay {
                RoundedRectangle(cornerRadius: 10)
                    .stroke(
                        hasError ? .red : .gray.opacity(0.90), lineWidth: 1)
            }
            .padding(.top, 5)
            .foregroundColor(.black)
            if hasError {
                HStack {
                    Image(systemName: "exclamationmark.triangle.fill")
                        .font(.headline)
                    Text(validationMessage)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .font(.footnote)
                }.foregroundColor(.red)
            }
        }
    }
}

then I use it like so

TextField("test", text: self.$email)
                .textFieldStyle(
                    AppTextFieldStyle(
                        label: "Email",
                        fieldValue: self.$email,
                        systemImage: "person.circle",
                        hasError: .constant(true),
                        validationMessage: .constant("Please enter your email")
                    )
                )
                .focused($focus, equals: .email)
                .foregroundColor(Color(.label))
                .textInputAutocapitalization(.never)
                .privacySensitive()
                .disableAutocorrection(true)
            //
            SecureField("password", text: self.$password)
                .textFieldStyle(
                    AppTextFieldStyle(
                        label: "Password",
                        fieldValue: self.$password,
                        placeholderText: "password",
                        systemImage: "lock.circle",
                        isPassword: true,
                        hasError: .constant(false),
                        validationMessage: .constant("please enter your password")
                    )
                )
                .focused($focus, equals: .password)
                .textInputAutocapitalization(.never)
                .privacySensitive()
                .disableAutocorrection(true)

btw the focus state is defined in the form view

enum FocusableField: Hashable {
        case email
        case password
    }
    @FocusState private var focus: FocusableField?
Richard Torcato
  • 2,504
  • 25
  • 26