This is the 27th day of my participation in the Gwen Challenge.More article challenges
Let’s take a look at the UI
The Gif above shows something like this:
1. Click the login button on my page and push to the login page.
2. Click “Not registered” on the login page and push to the registration page. You can see that the registration page is very similar to the login page except that there is a textField that asks you to input your password again.
3. Go back to the login page and enter the mobile phone number in the mobile phone number input box. The password input box in the second line will enable only when the mobile phone number digit is correct and the password is not empty. Note that it’s not my fault that the password input is invisible in the screen recording, it’s the system recording that is.
4. Click login to make the interface request for login. After the request is successful, pop to my page.
5. After the “login success” Toast, request my integral interface to obtain relevant personal integral data.
Today, we are talking about the preparation of login and registration page and the binding logic of two inputs, one button and request of the login page.
Write base class pages for login and registration pages
Based on the above analysis, we can see that there are some similarities between login and registration page, based on the principle of writing less code to write less code, we can completely extract a base class to reuse, which contains two input boxes, 1 button and login interface and registration interface.
import UIKit import MBProgressHUD class AccountBaseController: BaseViewController {/// BaseViewController {/// BaseViewController {/// BaseViewController {/// BaseViewController; UITextField at = {let textField = UITextField at () textField. Layer. The borderWidth = 0.5 textField. Layer. The borderColor = UIColor.gray.cgColor textField.backgroundColor = .white textField.keyboardType = .numberPad textField.returnKeyType = .done textField. Font = uifont.systemFont (ofSize: 15) textField. Placeholder = "Please input username" let emptyView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 1)) textField.leftView = emptyView textField.rightView = emptyView textField.leftViewMode = .always TextField. RightViewMode =. Always return textField}() /// lazy var passwordField: UITextField at = {let textField = UITextField at () textField. Layer. The borderWidth = 0.5 textField. Layer. The borderColor = UIColor.gray.cgColor textField.backgroundColor = .white textField.returnKeyType = .done textField.font = UIFont.systemFont(ofSize: 15) textField. IsSecureTextEntry = true textField. Placeholder = "please enter the password" let emptyView = UIView (frame: CGRect (x: 0, y: 0, width: 10, height: 1)) textField.leftView = emptyView textField.rightView = emptyView textField.leftViewMode = .always RightViewMode =. Always return textField}() /// Lazy var actionButton: UIButton = { let button = UIButton(type: .custom) button.setTitleColor(.white, for: .normal) button.isEnabled = false button.layer.cornerRadius = 22 button.layer.masksToBounds = true return button }() Override func viewDidLoad() {super.viewdidLoad () setupUI()} override func viewDidLoad() {super.viewdidLoad () setupUI()} Is not in the base class for private func setupUI () {the addSubview (usernameFiled) usernameFiled. SNP. MakeConstraints {make in make.top.equalTo(view).offset(kTopMargin + 16) make.leading.equalTo(view).offset(16) make.trailing.equalTo(view).offset(-16) make.height.equalTo(44) } usernameFiled.layer.cornerRadius = 22 usernameFiled.layer.masksToBounds = true view.addSubview(passwordField) passwordField.snp.makeConstraints { make in make.top.equalTo(usernameFiled.snp.bottom).offset(16) make.leading.trailing.height.equalTo(usernameFiled) } passwordField.layer.cornerRadius = 22 passwordField.layer.masksToBounds = true } } extension AccountBaseController { /// Func login(username: String, password: String) { accountProvider.rx.request(AccountService.login(username, password)) .map(BaseModel<AccountInfo>.self) .subscribe { baseModel in if baseModel.errorCode == 0 { /// Here single login information saved in the back of the accounts module, first write here, do not affect process AccountManager. Shared. SaveLoginUsernameAndPassword (info: baseModel.data, username: username, password: Password) /// I am trying to subscribe to the main thread and use MB, but there is still a problem, I can not use the GCD, experienced friends please can teach me. DispatchQueue. Main. Async {MBProgressHUD. ShowText (" login succeeds ")} / / / self. The back page navigationController? .popToRootViewController(animated: true) } } onError: { _ in }.disposed(by: Func registerAndLogin(username: String, password: String, repassword: String) String) { accountProvider.rx.request(AccountService.register(username, password, repassword)) .map(BaseModel<AccountInfo>.self) .subscribe { baseModel in if baseModel.errorCode == 0 { Dispatchqueue.main. async {mbProgresshud. showText(" register successfully ")} self.login(username: username, password: password) } } onError: { _ in }.disposed(by: rx.disposeBag) } }Copy the code
There is no particularly complicated logic in AccountBaseController, except that in the subscription to the login interface:
-
I’ve written out some of the logic of AccountManager ahead of time, but it doesn’t affect the process (the account management module).
-
In addition, in the subscription, I switch to the main thread through GCD to use MB, I try to subscribe in the main thread, not using GCD to play MB, but there is a problem, if you know the big guy also trouble to teach me.
I also want to know how to use MBProgressHUD correctly in RxSwift subscriptions.
The login page
With the AccountBaseController above, we write the LoginControll is basically more concerned with the enablement logic between the two input fields and the login button. Pay attention to the notes.
import UIKit import RxSwift import RxCocoa import MBProgressHUD class LoginController: AccountBaseController {private lazy var toRegisterButton: UIButton = {let button = UIButton(type: .custom) let attString = NSAttributedString(string: "Not registered?" , attributes: [.underlineStyle: NSUnderlineStyle.single.rawValue, .foregroundColor: UIColor.systemBlue, .font: UIFont.systemFont(ofSize: 15)]) button.setAttributedTitle(attString, for: .normal) return button }() override func viewDidLoad() { super.viewDidLoad() setupUI() } private func setupUI() { /// Actionbutton. setTitle(title, for:) actionButton.setTitle(title, for:) Normal) / / / there's no sign up button layout view. AddSubview (toRegisterButton) toRegisterButton. SNP. MakeConstraints {make in Make.top.equalto (passwordfield.snp.bottom).offset(16) make.trailing. EqualTo (usernameFiled)} /// The layout of the login button view.addSubview(actionButton) actionButton.snp.makeConstraints { make in make.top.equalTo(toRegisterButton.snp.bottom).offset(16) make.leading.trailing.height.equalTo(usernameFiled) } /// The string must be greater than or equal to 11 bits, display too much input by intercepting, and return true, Less than 11 returns false let usernameValid = usernameFiled. Rx. Text. OrEmpty. Map {[weak self] text - > Bool in the if text. Count > = {11 Print (" out, intercept ") self? .usernameFiled.text = String(text.prefix(11)) return true }else { return false } } .share(replay: 1) // Without this map would be executed once for each binding, rx is stateless by default You can play yourself, have a regular also can let passwordValid = passwordField. Rx. Text. OrEmpty. Map {$0. Count > 0}. Share (replay: 1) /// Use sequence combineLatest to combine the above two sequences, Let everythingValid = Observable.combineLatest(usernameValid, passwordValid) {$0 && $1}.share(replay: 1) / / / the validity of the user name and password input box can make binding usernameValid. Bind (to: passwordField. Rx. IsEnabled) disposed (by: Rx.disposebag) /// The valid map of the username becomes the background color binding of the password input box userNamevalid. map {$0? Uicolor.white: UIColor. Gray. WithAlphaComponent (0.5)}. The bind (to: passwordField. Rx. BackgroundColor) disposed (by: Rx. DisposeBag) / / / the validity of the two input box joint with button can make binding everythingValid. Bind (to: actionButton. Rx. IsEnabled) disposed (by: {$0? Uicolor.systemblue: {$0? Uicolor.systemblue: {$0? Uicolor.systemblue: {$0? UIColor. SystemBlue. WithAlphaComponent (0.5)}. The bind (to: actionButton. Rx. BackgroundColor) disposed (by: Rx.tap. Subscribe {[weak self] _ in) /// The button click event toregisterbutton.rx.tap. Subscribe {[weak self] _ in self?.navigationController?.pushViewController(RegisterController(), animated: true) }.disposed(by: Rx.tap. Subscribe (onNext: actionButton.rx.tap. Subscribe); { [weak self] _ in guard let username = self?.usernameFiled.text, let password = self?.passwordField.text else { return } self?.login(username: username, password: password) }) .disposed(by: rx.disposeBag) } }Copy the code
At this point, the layout and logic of the login page are basically complete. In this page, we are more bound to valid to complete the function of the page. You can imagine how much code will be written if you use the delegate of UITextField instead of RxSwift. The logic is going to be more complicated than this, because I used to write this logic in primitive terms myself.
In addition, I would like to say that this input binding logic, I am very early stolen teacher RxSwift Chinese official website example, hee hee.
The sign-up page
Now that the login page is complete, the registration page is just a little bit more logical than the login page, so here’s the code:
import UIKit import RxSwift import RxCocoa class RegisterController: AccountBaseController { private lazy var repasswordField: UITextField at = {let textField = UITextField at () textField. Layer. The borderWidth = 0.5 textField. Layer. The borderColor = UIColor.gray.cgColor textField.backgroundColor = .white textField.returnKeyType = .done textField.font = UIFont.systemFont(ofSize: 15) textField. IsSecureTextEntry = true textField. Placeholder = "please enter the password again let emptyView = UIView (frame: CGRect (x: 0, y: 0, width: 10, height: 1)) textField.leftView = emptyView textField.rightView = emptyView textField.leftViewMode = .always textField.rightViewMode = .always return textField }() override func viewDidLoad() { super.viewDidLoad() setupUI() } Private func setupUI() {title = "register" actionButton.setTitle(title, for: .normal) view.addSubview(repasswordField) repasswordField.snp.makeConstraints { make in make.top.equalTo(passwordField.snp.bottom).offset(16) make.leading.trailing.height.equalTo(usernameFiled) } repasswordField.layer.cornerRadius = 22 repasswordField.layer.masksToBounds = true view.addSubview(actionButton) actionButton.snp.makeConstraints { make in make.top.equalTo(repasswordField.snp.bottom).offset(16) make.leading.trailing.height.equalTo(usernameFiled) } let usernameValid = usernameFiled.rx.text.orEmpty .map { [weak Self] text -> Bool in if text. Count >= 11 {print(" ") self? .usernameFiled.text = String(text.prefix(11)) return true }else { return false } } .share(replay: 1) // without this map would be executed once for each binding, rx is stateless by default let passwordValid = passwordField.rx.text.orEmpty .map { $0.count > 0 } .share(replay: 1) let repasswordValid = repasswordField.rx.text.orEmpty .map { $0.count > 0 } .share(replay: 1) let isSamePassword = Observable.combineLatest(passwordField.rx.text.orEmpty, repasswordField.rx.text.orEmpty) { $0 == $1 }.share(replay: 1) let everythingValid = Observable.combineLatest(usernameValid, passwordValid, repasswordValid, isSamePassword) { $0 && $1 && $2 && $3 } .share(replay: 1) usernameValid .bind(to: passwordField.rx.isEnabled) .disposed(by: rx.disposeBag) usernameValid.map { $0 ? UIColor.white : UIColor. Gray. WithAlphaComponent (0.5)}. The bind (to: passwordField. Rx. BackgroundColor) disposed (by: Rx. UsernameValid disposeBag). The map {$0? UIColor. White: UIColor. Gray. WithAlphaComponent (0.5)}. Bind (to: repasswordField.rx.backgroundColor) .disposed(by: rx.disposeBag) everythingValid .bind(to: actionButton.rx.isEnabled) .disposed(by: rx.disposeBag) everythingValid.map { $0 ? UIColor.systemBlue : UIColor. SystemBlue. WithAlphaComponent (0.5)}. The bind (to: actionButton. Rx. BackgroundColor) disposed (by: rx.disposeBag) actionButton.rx.tap .subscribe(onNext: { [weak self] _ in guard let username = self?.usernameFiled.text, let password = self?.passwordField.text, let repassword = self?.repasswordField.text else { return } self?.registerAndLogin(username: username, password: password, repassword: repassword) }) .disposed(by: rx.disposeBag) } }Copy the code
conclusion
We have prepared the login and registration page today:
-
By writing the base class AccountBaseController, we extract the similarities between the two pages and define the interfaces.
-
On the login page, the unique controls on the login page are laid out and various binding relationships are completed to ensure logical correctness. The Observable.combineLatest function is worth knowing how to use.
-
The registration page, whose content is very similar to the logic and login pages, doesn’t require much ink.
Tomorrow to continue
After completing the login and registration page, we actually need to do a tracking management of the account. How to achieve this is the content of the next article.
Come on, everybody!