Combine Customize handling of asynchronous events by combining event-processing operators. https://developer.apple.com/documentation/combine
Navajo-Swift - Password Validator & Strength Evaluator https://github.com/jasonnam/Navajo-Swift
ContentView.swift
// // ContentView.swift // Swiftuitest // // Created by Cairocoders // import SwiftUI struct ContentView: View { @ObservedObject private var userViewModel = UserViewModel() @State var presentAlert = false var body: some View { Form { Section(footer: Text(userViewModel.usernameMessage).foregroundColor(.red)) { TextField("Username", text: $userViewModel.username) .autocapitalization(.none) } Section(footer: Text(userViewModel.passwordMessage).foregroundColor(.red)) { SecureField("Password", text: $userViewModel.password) SecureField("Password again", text: $userViewModel.passwordAgain) } Section { Button(action: { self.signUp() }) { Text("Sign up") }.disabled(!self.userViewModel.isValid) } } .sheet(isPresented: $presentAlert) { WelcomeView() } } func signUp() { self.presentAlert = true } } struct WelcomeView: View { var body: some View { Text("Welcome! Great to have you on board!") } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }UserViewModel.swift
// // UserViewModel.swift // Swiftuitest // // Created by Cairocoders // import Foundation import Combine//Customize handling of asynchronous events by combining event-processing operators. import Navajo_Swift class UserViewModel: ObservableObject { // input @Published var username = "" @Published var password = "" @Published var passwordAgain = "" // output @Published var usernameMessage = "" @Published var passwordMessage = "" @Published var isValid = false private var cancellableSet: Set<AnyCancellable> = [] private var isUsernameValidPublisher: AnyPublisher<Bool, Never> { $username .debounce(for: 0.8, scheduler: RunLoop.main) .removeDuplicates() .map { input in return input.count >= 3 } .eraseToAnyPublisher() } private var isPasswordEmptyPublisher: AnyPublisher<Bool, Never> { $password .debounce(for: 0.8, scheduler: RunLoop.main) .removeDuplicates() .map { password in return password == "" } .eraseToAnyPublisher() } private var arePasswordsEqualPublisher: AnyPublisher<Bool, Never> { Publishers.CombineLatest($password, $passwordAgain) .debounce(for: 0.2, scheduler: RunLoop.main) .map { password, passwordAgain in return password == passwordAgain } .eraseToAnyPublisher() } private var passwordStrengthPublisher: AnyPublisher<PasswordStrength, Never> { $password .debounce(for: 0.2, scheduler: RunLoop.main) .removeDuplicates() .map { input in return Navajo.strength(ofPassword: input) } .eraseToAnyPublisher() } private var isPasswordStrongEnoughPublisher: AnyPublisher<Bool, Never> { passwordStrengthPublisher .map { strength in print(Navajo.localizedString(forStrength: strength)) switch strength { case .reasonable, .strong, .veryStrong: return true default: return false } } .eraseToAnyPublisher() } enum PasswordCheck { case valid case empty case noMatch case notStrongEnough } private var isPasswordValidPublisher: AnyPublisher<PasswordCheck, Never> { Publishers.CombineLatest3(isPasswordEmptyPublisher, arePasswordsEqualPublisher, isPasswordStrongEnoughPublisher) .map { passwordIsEmpty, passwordsAreEqual, passwordIsStrongEnough in if (passwordIsEmpty) { return .empty } else if (!passwordsAreEqual) { return .noMatch } else if (!passwordIsStrongEnough) { return .notStrongEnough } else { return .valid } } .eraseToAnyPublisher() } private var isFormValidPublisher: AnyPublisher<Bool, Never> { Publishers.CombineLatest(isUsernameValidPublisher, isPasswordValidPublisher) .map { userNameIsValid, passwordIsValid in return userNameIsValid && (passwordIsValid == .valid) } .eraseToAnyPublisher() } init() { isUsernameValidPublisher .receive(on: RunLoop.main) .map { valid in valid ? "" : "User name must at least have 3 characters" } .assign(to: \.usernameMessage, on: self) .store(in: &cancellableSet) isPasswordValidPublisher .receive(on: RunLoop.main) .map { passwordCheck in switch passwordCheck { case .empty: return "Password must not be empty" case .noMatch: return "Passwords don't match" case .notStrongEnough: return "Password not strong enough" default: return "" } } .assign(to: \.passwordMessage, on: self) .store(in: &cancellableSet) isFormValidPublisher .receive(on: RunLoop.main) .assign(to: \.isValid, on: self) .store(in: &cancellableSet) } }