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)