I am trying to implement MVVM-C design pattern with Firebase Authentication using Mobile phone.
For testing the authentication process I created a single-view UI for sending mobile number (view for country code & mobile number combined in a single string) and second-view for submitting OTP (a stack-view with six-text-fields in a stack) all on a single scene and it works using a single viewController.
However, to improve the look and feel. I have separated UI for entering mobile number and OTP on two seperate viewControllers and the verification process has stoped working.
First view is successfully sending the mobile-number to Firebase and receiving the OTP.
So I assume that RequestCodeViewController.swift is working perfectly as I am receiving the OTP.
However, when I enter that received "OTP" in the second view VerifyMobileViewController.swift (using TextFieldStackView) and press the done button (verify OTP) to authenticate then there is nothing happening. "Done" button action does nothing.
I tried to debug by adding several error messages at different stages of the button action but failed... (tried two days...sorry, am a beginner)
I believe as per my limited Swift knowledge that it is somehow not sending the full OTP in a single string or in one package...(I could be wrong)
ISSUE:Code does not work at "Done" button press (Button Action) on the VerifyMobileViewController.swift
I am receiving the OTP successfully. However, unable to verify mobile using the received OTP (VerifyMobileViewController.swift).
My code is as follows:
Storyboard.swift
import UIKit
enum AppStoryboard : String {
case Main, HelpPage, PrivacyStatement, Booking, JobHostOne, Login
var instance : UIStoryboard {
return UIStoryboard(name: self.rawValue, bundle: Bundle.main)
}
func viewController<T : UIViewController>(viewControllerClass : T.Type) -> T {
let storyboardID = (viewControllerClass as UIViewController.Type).storyboardID
guard let scene = instance.instantiateViewController(withIdentifier: storyboardID) as? T else {
fatalError("ViewController with identifier \(storyboardID), not found in \(self.rawValue) Storyboard")
}
return scene
}
func initialViewController() -> UIViewController? {
return instance.instantiateInitialViewController()
}
}
extension UIViewController {
// Not using static as it wont be possible to override to provide custom storyboardID then
class var storyboardID : String {
return "\(self)"
}
static func instantiate(fromAppStoryboard appStoryboard: AppStoryboard) -> Self {
return appStoryboard.viewController(viewControllerClass: self)
}
}
Cordinator.swift
import Foundation
class Coordinator {
private(set) var childCoordinators: [Coordinator] = []
func start() {
preconditionFailure("This method needs to be overriden by concrete subclass.")
}
func finish() {
preconditionFailure("This method needs to be overriden by concrete subclass.")
}
func addChildCoordinator(_ coordinator: Coordinator) {
childCoordinators.append(coordinator)
}
func removeChildCoordinator(_ coordinator: Coordinator) {
if let index = childCoordinators.index(where: { (element) -> Bool in
return element == coordinator
}) {
childCoordinators.remove(at: index)
}
else {
preconditionFailure("Couldn't remove coordinator: \(coordinator). It's not a child coordinator.")
}
}
func removeAllChildCoordinatorsWith<T>(type: T.Type) {
childCoordinators = childCoordinators.filter { $0 is T == false }
}
func removeAllChildCoordinators() {
childCoordinators.removeAll()
}
}
extension Coordinator: Equatable {
static func == (lhs: Coordinator, rhs: Coordinator) -> Bool {
return lhs === rhs
}
}
AppCoordinator.swift
import UIKit
class AppCoordinator: Coordinator {
// MARK: - Properties
var window: UIWindow
// MARK: - Coordinator
init(window: UIWindow?) {
self.window = window!
}
override func start() {
let loginCoordinator = LoginCoordinator(window: window)
loginCoordinator.coordinatorDelegate = self
loginCoordinator.start()
}
override func finish() {
}
}
extension AppCoordinator: LoginCoordinatorDelegate {
}
Constants.swift
import Foundation
//MARK:- UserDefaults Keys
let authenticationIdKey = "authVerificationID"
//MARK:- UIStoryBoard Indetifier
let loginStroyBoard = "Login"
//MARK:- UIViewController Indentifier
let loginIndetifier = "RequestCodeViewController"
//MARK: Error Messages
let authenticationFailedMsg = "Authentication failed."
let phonenumberNotValidMsg = "Please enter valid phone number."
let verificationCodeEmptyMsg = "Validation code is empty."
let verificationCodeNotValidMsg = "Validation code is wrong"
//MARK: Success Messages
let authenticationSuccessMsg = "Authentication successfull."
Utils.swift
import UIKit
class Utils {
static func showProgress() {
SVProgressHUD.show()
}
static func dismissProgress() {
SVProgressHUD.dismiss()
}
static func showSingleAlert(message: String, controller: UIViewController) {
let alertController = UIAlertController(title: "iGuess", message: message, preferredStyle: .alert)
let okButton = UIAlertAction(title: "Ok", style: .default) { (action) in
alertController.dismiss(animated: true, completion: nil)
}
alertController.addAction(okButton)
controller.present(alertController, animated: true, completion: nil)
}
}
extension String {
func trimText()-> String {
return self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
subscript (i: Int) -> Character {
return self[index(startIndex, offsetBy: i)]
}
}
RequestCodeViewController.swift
import UIKit
import CoreTelephony
class RequestCodeViewController: UIViewController {
@IBOutlet weak var countryCodeTextField: UITextField!
@IBOutlet weak var phoneTextField: UITextField!
//MARK:- Properties
var viewModel: LoginViewModel? {
willSet {
viewModel?.viewDelegate = nil
}
didSet {
viewModel?.viewDelegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setUpUI()
self.navigationItem.title = "iGuestBooking"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setUpUI() {
phoneTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
countryCodeTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
let networkInfo = CTTelephonyNetworkInfo()
if let carrier = networkInfo.subscriberCellularProvider {
self.viewModel?.countryCode = "+\(carrier.mobileCountryCode!)"
}
phoneTextField.text = self.viewModel?.phoneNumber
countryCodeTextField.text = self.viewModel?.countryCode
}
//MARK:- Actions
@IBAction func sendVerificationButtonAction(_ sender: Any) {
if self.phoneTextField.text!.trimText().isEmpty || self.countryCodeTextField.text!.trimText().isEmpty {
Utils.showSingleAlert(message: phonenumberNotValidMsg, controller: self)
return
}
viewModel?.sendVerificationCode(callBack: { (isSuccess) -> (Void) in
if isSuccess {
//self.refreshCodeTextFields()
let verifyMobileScene = VerifyMobileViewController.instantiate(fromAppStoryboard: .Login)
self.present(verifyMobileScene, animated: true, completion: nil)
}
else {
self.dismiss(animated: true, completion: nil)
}
})
}
@IBAction func textFieldDidChange(_ sender: UITextField) {
if sender == phoneTextField {
viewModel?.phoneNumber = sender.text!
}
else {
viewModel?.countryCode = sender.text!
}
}
// MARK: - to dismiss the keyboard BEGIN
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
// MARK: - to dismiss the keyboard ENDS
}
//MARK:- LoginViewModelViewDelgate
extension RequestCodeViewController: LoginViewModelViewDelegate {
}
VerifyMobileViewController.swift
import UIKit
import CoreTelephony
class VerifyMobileViewController: UIViewController {
// MARK: - to dismiss the keyboard BEGIN
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
// MARK: - to dismiss the keyboard ENDS
@IBOutlet weak var codeTextField: UITextField!
@IBOutlet weak var textFieldStackView: UIStackView!
@IBOutlet weak var doneButton: UIButton!
//MARK:- Properties
var viewModel: LoginViewModel? {
willSet {
viewModel?.viewDelegate = nil
}
didSet {
viewModel?.viewDelegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
setUpUI()
self.navigationItem.title = "iLogin"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func setUpUI() {
codeTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
self.textFieldStackView.isHidden = false
self.codeTextField.isHidden = false
self.doneButton.isHidden = false
}
func refreshCodeTextFields() {
self.codeTextField.text = ""
for textField in textFieldStackView.subviews as! [UITextField] {
textField.text = ""
// textField.delegate = self
}
}
@IBAction func doneButtonAction(_ sender: Any) {
var verificationCode: String = ""
for textField in textFieldStackView.subviews as! [UITextField]{
if (textField.text?.isEmpty)! {
Utils.showSingleAlert(message: verificationCodeEmptyMsg, controller: self) //
return
}
verificationCode.append(textField.text ?? "")
print(textField.text ?? "") //
//Utils.showSingleAlert(message: verificationCodeNotValidMsg, controller: self) //
}
viewModel?.verifyCode(code: verificationCode,callBack: {
[weak self]
(user, error) -> Void in
guard let self_ = self else { return }
if error == nil {
print(user ?? "") //
self_.refreshCodeTextFields()
self_.textFieldStackView.isHidden = false
self_.codeTextField.isHidden = false
Utils.showSingleAlert(message: authenticationSuccessMsg, controller: self_)
}
else {
Utils.showSingleAlert(message: authenticationFailedMsg, controller: self_)
print(error?.localizedDescription ?? "")
}
})
}
//
@IBAction func textFieldDidChange(_ sender: UITextField) {
if sender == codeTextField {
for tampTextField in textFieldStackView.subviews as! [UITextField] {
if sender.text!.count > (tampTextField.tag - 1) {
if let tamp = sender.text?.index(sender.text!.startIndex, offsetBy: tampTextField.tag - 1) {
tampTextField.text = String(describing: sender.text![tamp])
}
}
else {
tampTextField.text = ""
}
}
}
}
//
}
//MARK:- UITextFieldDelegate
extension VerifyMobileViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if range.location > 5 {
return false
}
return true
}
}
//MARK:- LoginViewModelViewDelgate
extension VerifyMobileViewController: LoginViewModelViewDelegate {
}
I am an absolute beginner and just started learning Swift. Thanking you kindly all for all the help in advance.
Comments
Post a Comment