Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changed rootViewController but the view of old rootViewController is still in the View Hierarchy

I have a basic situation, when the user has been authenticated, I remove and change the current screen (the login screen) to another screen inside the app.

To do this, I use this code:

if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
    print("Window's subviews before removed = \(appDelegate.window?.subviews)")

    appDelegate.window?.subviews.forEach { $0.removeFromSuperview() }

    print("Window's subviews after removed = \(appDelegate.window?.subviews)")

    appDelegate.window?.rootViewController?.view?.removeFromSuperview()
    appDelegate.window?.rootViewController?.removeFromParentViewController()

    appDelegate.window?.rootViewController = newRootViewController

    print("Window's subviews after changed = \(appDelegate.window?.subviews)")
}

This is the output:

enter image description here

This is what the user can see on the device screen - looks very OK: enter image description here

However, it isn't ok in the Debug View Hierarchy tool:

enter image description here

As you can see, the view of old rootViewController is still there, inside UIWindow but not a subview of it - as the output has indicated.

This behavior seems strange, has anyone experienced this problem yet?

like image 628
Anh Pham Avatar asked May 11 '18 09:05

Anh Pham


1 Answers

The reason:

I have this problem when I try to replace rootViewController while logged in using the Google Sign-In SDK and Facebook Login SDK.

These SDKs have an authentication screen like this one:

enter image description here

By using the Debug View Hierarchy, I realized when the authentication screen was presented (2), the app changed the rootViewController to a UITransitionView. And when the authentication screen is dismissed (3), it changed the rootViewController once more to the state before presenting the authentication screen (1).

State (1): Before presenting the authentication screen, rootViewController is LoginViewController.

State (2): Presenting the authentication screen, rootViewController changed to UITransitionView.

State (3): After dismissed the authentication screen, rootViewController has returned to LoginViewController.

States (1) + (3) in Debug View Hierarchy: enter image description here

State (2) in Debug View Hierarchy: enter image description here

I put my code to change the rootViewController in the each delegate method of corresponding SDKs that is called when authentication is completed.

Google Sign-In SDK: signIn:didSignInForUser:withError:

Facebook Login SDK: logInWithReadPermissions:fromViewController:handler:

These methods are called immediately after the user has authenticated without regard to the authentication screen is dismissed or not.

It means, sometimes, the problem occurs when the user's authentication process is completed too quickly, even before the authentication screen is dismiss and the rootViewController changes to LoginViewController. It means, the problem is in between two states (2) and (3), when the user has authenticated but rootViewController is still UITransitionView.

The solution:

Temporary, before I can find a better solution, I prevent the user's authentication process from happening too quickly, it means that I wait for state (3) to finish by delaying 0.25 seconds after the user had authenticated and then changing rootViewController.

0.25 is enough time for everything to work and too fast for the user to lose patience.

like image 113
Anh Pham Avatar answered Oct 30 '22 04:10

Anh Pham