diff --git a/SwiftUIBasics.xcodeproj/project.xcworkspace/xcuserdata/diplomado.xcuserdatad/UserInterfaceState.xcuserstate b/SwiftUIBasics.xcodeproj/project.xcworkspace/xcuserdata/diplomado.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..f53fbab Binary files /dev/null and b/SwiftUIBasics.xcodeproj/project.xcworkspace/xcuserdata/diplomado.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/SwiftUIBasics/Views/Components/FormTextField.swift b/SwiftUIBasics/Views/Components/FormTextField.swift index cdd161c..ed9b007 100644 --- a/SwiftUIBasics/Views/Components/FormTextField.swift +++ b/SwiftUIBasics/Views/Components/FormTextField.swift @@ -5,12 +5,14 @@ // Created by Diplomado on 09/12/23. // + import SwiftUI struct FormTextField: View { var name = "" @Binding var value: String var isSecure = false + var fieldType: UIKeyboardType = .default var body: some View { VStack { @@ -20,6 +22,7 @@ struct FormTextField: View { .padding(.horizontal) } else { TextField(name, text: $value) + .keyboardType(fieldType) .font(.system(size: 20, weight: .semibold, design: .rounded)) .padding(.horizontal) } @@ -33,7 +36,7 @@ struct FormTextField: View { #Preview { VStack(spacing: 20) { - FormTextField(name: "Email", value: .constant(""), isSecure: false) + FormTextField(name: "Email", value: .constant(""), isSecure: false, fieldType: .emailAddress) FormTextField(name: "Password", value: .constant(""), isSecure: true) } } diff --git a/SwiftUIBasics/Views/SignUpView.swift b/SwiftUIBasics/Views/SignUpView.swift index 6cef5d9..5aeecbb 100644 --- a/SwiftUIBasics/Views/SignUpView.swift +++ b/SwiftUIBasics/Views/SignUpView.swift @@ -2,34 +2,41 @@ // SignUpView.swift // SwiftUIBasics // -// Created by Diplomado on 09/12/23. +// Created by Tere DurĂ¡n on 09/12/23. // import SwiftUI import Combine class SignUpViewModel: ObservableObject { - // inputs - @Published var username: String = "" + // Inputs + @Published var email: String = "" @Published var password: String = "" @Published var passwordConfirm: String = "" - - // outputs - @Published var isValidUsernameLength: Bool = false + + // Outputs + @Published var isValidEmail: Bool = false + @Published var isValidPassword: Bool = false @Published var isValidPasswordLength: Bool = false @Published var isValidPasswordUpperCase: Bool = false + @Published var isValidPasswordLowerCase: Bool = false + @Published var isValidPasswordCase: Bool = false + @Published var passwordContainsSymbol: Bool = false + @Published var passwordContainsNumber: Bool = false @Published var isValidPasswordMatch: Bool = false @Published var isValid: Bool = false private var cancelableSet: Set = [] init() { - $username + $email .receive(on: RunLoop.main) - .map { username in - return username.count >= 4 + .map { email in + let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" + let emailFormat = NSPredicate(format:"SELF MATCHES %@", pattern) + return emailFormat.evaluate(with: email) } - .assign(to: \.isValidUsernameLength, on: self) + .assign(to: \.isValidEmail, on: self) .store(in: &cancelableSet) $password @@ -52,6 +59,46 @@ class SignUpViewModel: ObservableObject { } .assign(to: \.isValidPasswordUpperCase, on: self) .store(in: &cancelableSet) + + $password + .receive(on: RunLoop.main) + .map { password in + let pattern = "[a-z]" + if let _ = password.range(of: pattern, options: .regularExpression) { + return true + } else { + return false + } + } + .assign(to: \.isValidPasswordLowerCase, on: self) + .store(in: &cancelableSet) + + $password + .receive(on: RunLoop.main) + .map { password in + let pattern = /\W+/ + let symbols = Regex(pattern) + if password.contains(symbols) { + return true + } else { + return false + } + } + .assign(to: \.passwordContainsSymbol, on: self) + .store(in: &cancelableSet) + + $password + .receive(on: RunLoop.main) + .map { password in + let pattern = "[0-9]" + if let _ = password.range(of: pattern, options: .regularExpression) { + return true + } else { + return false + } + } + .assign(to: \.passwordContainsNumber, on: self) + .store(in: &cancelableSet) Publishers.CombineLatest($password, $passwordConfirm) .receive(on: RunLoop.main) @@ -60,8 +107,16 @@ class SignUpViewModel: ObservableObject { } .assign(to: \.isValidPasswordMatch, on: self) .store(in: &cancelableSet) + + Publishers.CombineLatest4($isValidPasswordUpperCase, $isValidPasswordLowerCase, $passwordContainsSymbol, $passwordContainsNumber) + .receive(on: RunLoop.main) + .map { (uppercase, lowercase, symbol, number) in + return uppercase && lowercase && symbol && number + } + .assign(to: \.isValidPasswordCase, on: self) + .store(in: &cancelableSet) - Publishers.CombineLatest4($isValidUsernameLength, $isValidPasswordLength, $isValidPasswordUpperCase, $isValidPasswordMatch) + Publishers.CombineLatest4($isValidEmail, $isValidPasswordLength, $isValidPasswordCase, $isValidPasswordMatch) .map { (a, b, c, d) in return a && b && c && d } @@ -80,13 +135,16 @@ struct SignUpView: View { .bold() .foregroundStyle(.maryBlue) .padding(.bottom, 30) - FormTextField(name: "Username", value: $vm.username) - RequirementText(text: "A minimum of 4 characters", isValid: vm.isValidUsernameLength) + FormTextField(name: "Email", value: $vm.email, fieldType: .emailAddress) + RequirementText(text: "A valid email format", isValid: vm.isValidEmail) .padding() FormTextField(name: "Password", value: $vm.password, isSecure: true) VStack { RequirementText(text: "A minimum of 8 characters", isValid: vm.isValidPasswordLength) - RequirementText(text: "One uppercase letter", isValid: vm.isValidPasswordUpperCase) + RequirementText(text: "At least one uppercase letter", isValid: vm.isValidPasswordUpperCase) + RequirementText(text: "At lest one lowercase letter", isValid: vm.isValidPasswordLowerCase) + RequirementText(text: "At least one symbol", isValid: vm.passwordContainsSymbol) + RequirementText(text: "At least one digit", isValid: vm.passwordContainsNumber) } .padding() FormTextField(name: "Confirm Password", value: $vm.passwordConfirm, isSecure: true) @@ -95,7 +153,6 @@ struct SignUpView: View { .padding(.bottom, 50) Button(action: { print("Doing") - // Proceed to the next screen }) { Text("Sign Up") .font(.system(.body, design: .rounded)) @@ -104,7 +161,6 @@ struct SignUpView: View { .padding() .frame(minWidth: 0, maxWidth: .infinity) .background(vm.isValid ? .maryBlue :.turquoise) - // .background(LinearGradient(gradient: Gradient(colors: [.turquoise, .maryBlue]), startPoint: .leading, endPoint: .trailing)) .cornerRadius(10) .padding(.horizontal) } @@ -115,7 +171,6 @@ struct SignUpView: View { .font(.system(.body, design: .rounded)) .bold() Button(action: { - // Proceed to Sign in screen }) { Text("Sign in") .font(.system(.body, design: .rounded)) @@ -132,3 +187,5 @@ struct SignUpView: View { #Preview { SignUpView() } + +