// // LoginForm.swift // Login // // Created by Javier Cicchelli on 01/12/2022. // Copyright © 2022 Röck+Cöde. All rights reserved. // import SwiftUI struct LoginForm: View { // MARK: States @FocusState private var focusedField: Field? // MARK: Bindings @Binding var username: String @Binding var password: String @Binding var errorMessage: String? // MARK: Properties let onReturn: () -> Void // MARK: Body var body: some View { VStack( alignment: .leading, spacing: 16 ) { TextField( NSLocalizedString( "login.text_field.username.placeholder", bundle: .module, comment: "The placeholder for the username text field." ), text: $username ) .textContentType(.username) .lineLimit(1) .autocapitalization(.none) .disableAutocorrection(true) .keyboardType(.default) .focused($focusedField, equals: .username) .onSubmit { onUsernameReturnPressed() } Divider() SecureField( NSLocalizedString( "login.text_field.password.placeholder", bundle: .module, comment: "The placeholder for the password text field." ), text: $password ) .textContentType(.password) .lineLimit(1) .autocapitalization(.none) .disableAutocorrection(true) .keyboardType(.default) .focused($focusedField, equals: .password) .onSubmit { onPasswordReturnPressed() } if let errorMessage { Divider() Text( NSLocalizedString( errorMessage, bundle: .module, comment: "The error message received from the backend." ) ) .foregroundColor(.secondary) .font(.body) .multilineTextAlignment(.center) .frame(maxWidth: .infinity) .padding(.horizontal, 24) } } .frame(maxWidth: .infinity, alignment: .leading) .padding(16) .background(Color.secondary.colorInvert()) .cornerRadius(8) .onAppear { setClearButtonIfNeeded() } .onChange(of: focusedField) { _ in onTextFieldFocused() } } } // MARK: - Helpers private extension LoginForm { func setClearButtonIfNeeded() { let textFieldAppearance = UITextField.appearance() guard textFieldAppearance.clearButtonMode != .whileEditing else { return } textFieldAppearance.clearButtonMode = .whileEditing } func onTextFieldFocused() { guard errorMessage != nil else { return } password = "" errorMessage = nil } func onUsernameReturnPressed() { guard !username.isEmpty else { return } if password.isEmpty { focusedField = .password } else { onReturn() } } func onPasswordReturnPressed() { guard !password.isEmpty else { return } if username.isEmpty { focusedField = .username } else { onReturn() } } } // MARK: - Enumerations private extension LoginForm { enum Field: Hashable { case username case password } } // MARK: - Previews struct LoginForm_Previews: PreviewProvider { static var previews: some View { LoginForm( username: .constant("Some username"), password: .constant("Some Password"), errorMessage: .constant(nil), onReturn: {} ) .previewDisplayName("Login form with no error message") LoginForm( username: .constant("Some username"), password: .constant("Some Password"), errorMessage: .constant("Some error goes in here..."), onReturn: {} ) .previewDisplayName("Login form with some error message") } }