I have a LoginCoordinator class whose start() method instantiates the LoginViewController. But even after assigning the coordinator variable in LoginViewController from LoginCoordinator, 'sometimes' the coordinator variable is nil in the view-controller.
By 'sometimes' I mean, when I debug and keep a breakpoint inside the start() method then, the value is assigned.
This is my LoginCoordinator
class LoginCoordinator: Coordinator {
var navigationController: UINavigationController
weak var parent: MainCoordinator?
var childCoordinators: [Coordinator]?
init(navController: UINavigationController) {
self.navigationController = navController
}
func start() {
let vc: LoginViewController = UIStoryboard.main.instantiateViewController()
vc.coordinator = self
navigationController.pushViewController(vc,animated: true)
}
func goToSignUp() {
let signUpCoordinator = SignUpCoordinator(navController: navigationController)
childCoordinators?.append(signUpCoordinator)
signUpCoordinator.start()
}
func goToHomeVC() {
let homeCoordinator = HomeCoordinator(navControl: navigationController)
childCoordinators?.append(homeCoordinator)
homeCoordinator.start()
}
}
This is my LoginViewController
class LoginViewController: UIViewController {
@IBOutlet weak private var userName: HubTextField!
@IBOutlet weak private var password: HubTextField!
@IBOutlet weak private var forgotPassword: UILabel!
@IBOutlet weak var loginButton: HubButton!
var viewModel: LoginSignupVM = .init(totalItem: 2)
weak var coordinator: LoginCoordinator?
fileprivate func setupForgotPassword() {
forgotPassword.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(forgotTapped))
forgotPassword.addGestureRecognizer(tapGesture)
}
override func viewDidLoad() {
super.viewDidLoad()
loginButton.appearance = Appearance(cornerRadius: loginButton.frame.height/2)
loginButton.setTitle(TitleText.loginText, for: .normal)
loginButton.isEnabled = false
setupTextField()
hideKeyboardWhenTappedAround()
setupForgotPassword()
setupNavigationBar()
// MARK: remove this later
userName.text = "[email protected]"
password.text = "asdfg123"
}
private func setupNavigationBar() {
let titleView = UILabel(frame: .zero)
titleView.text = "Login Up"
titleView.font = UIFont.systemFont(ofSize: 20)
navigationItem.titleView = titleView
rightButton()
}
fileprivate func rightButton() {
let rightButton = UIBarButtonItem(title: "signUp", style: .plain, target: self, action: #selector(goToSignUPScreen))
rightButton.title = "sign Up"
navigationItem.rightBarButtonItem = rightButton
}
@objc private func goToSignUPScreen() {
coordinator?.goToSignUp()
}
@objc private func backButtonClicked() {
navigationController?.popViewController(animated: true)
}
@objc private func forgotTapped() {
//MARK: todo forgot password
}
private func assignTags() {
userName.tag = 0
password.tag = 1
}
private func setupTextField() {
userName.placeHolder = TitleText.email
userName.textFieldType = .email
userName.delegate = self
password.placeHolder = TitleText.password
password.textFieldType = .password
password.delegate = self
assignTags()
}
private func goToHomeViewController() {
coordinator?.goToHomeVC()
}
@IBAction func onLoginClick(_ sender: Any) {
//MARK: todo login click change hardcoded verification
if let email = userName.text, let password = password.text {
if email == "[email protected]" && password == "asdfg123" {
goToHomeViewController()
}
}
}
}
extension LoginViewController: ValidatorButton {
func addNewValidated(tag: Int, result: ValidationResult) {
viewModel.validationList[tag] = result
loginButton.isEnabled = viewModel.loginButtonEnabled()
}
}
LoginCoordinator is called from MainCoordinator as:
class MainCoordinator: Coordinator {
var isLogin: Bool = false
var navigationController: UINavigationController
var childCoordinators: [Coordinator]? = []
let window: UIWindow?
init(navController: UINavigationController, window: UIWindow) {
self.navigationController = navController
self.window = window
}
func start() {
if isLogin {
let child = HomeCoordinator(navControl: navigationController)
childCoordinators?.append(child)
child.start()
} else {
let child = LoginCoordinator(navController:
navigationController)
childCoordinators?.append(child)
child.start()
}
}
}
And MainCoordinator is called from sceneDelegate as follows:
func scene(_ scene: UIScene, willConnectTo session:
UISceneSession, options connectionOptions:
UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: scene)
self.window = window
let navigationController = UINavigationController()
let mainCoordinator = MainCoordinator(navController:
navigationController,window: window)
mainCoordinator.start()
window.rootViewController = navigationController
window.makeKeyAndVisible()
}
CodePudding user response:
mainCoordinator is declared as a local variable in the willConnectTo delegate method:
let mainCoordinator = MainCoordinator(navController:
navigationController,window: window)
This means that it can be deallocated after the last access to it in willConnectTo.
Since mainCoordinator holds the only strong reference to LoginCoordinator, it will be deallocated too. This is why you see nil in the view controller - at that point, mainCoordinator has been deallocated.
You still see a non-nil value in LoginCoordinator.start, because at that point mainCoordinator.start still hasn't finished, so mainCoordinator is not deallocated yet.
To solve this, you can either make the scene delegate hold a strong reference to the main coordinator, by declaring a property called mainCoordinator:
var mainCoordinator: MainCoordinator!
// in willConnectTo:
mainCoordinator = MainCoordinator(navController:
navigationController,window: window)
