5

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?

Anh Pham
  • 2,004
  • 8
  • 16
  • 28
  • I think this `appDelegate.window?.subviews.forEach { $0.removeFromSuperview() }` is a very bad approach you should assing rootViewController directly basically you are removing the views in the windows directly? please if I am wrong correct me! – Reinier Melian May 11 '18 at 09:58
  • Yeah, I agree with you! but for debugging purpose, I try everything to make sure that all subviews of current rootViewController are deleted before assigning a new rootViewController. – Anh Pham May 11 '18 at 10:18
  • 1
    I will review it, I will let you know – Reinier Melian May 11 '18 at 10:19
  • @AnhPham I tried your code with a new project and it works as expected. Maybe problem is in another place. – trungduc May 11 '18 at 10:55
  • 1
    @trungduc + Reinier Melian, Thanks for looking at my code. I just discovered the problem is not entirely in changing the rootViewController. It related to Google Sign-In SDK. I'm digging deeper to find a good solution and I will update as soon as possible. – Anh Pham May 11 '18 at 11:08
  • @AnhPham Did you find a solution for this? I'm facing the same problem when integrating the Google Sign-In SDK. – Reynaldo Aguilar May 30 '18 at 16:34
  • @ReynaldoAguilar I have added my answer! – Anh Pham May 31 '18 at 02:44

2 Answers2

6

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.

Anh Pham
  • 2,004
  • 8
  • 16
  • 28
  • I've seen something similar when using UIView.transition to change my rootviewcontroller when accessed from a universal link. If the app is open and has a presented vc, I'm dismissing the vc but the UIView.transition seems to happen too fast to remove it from the view hierarchy. With your 0.25 seconds it still occurs, but making it 0.7, it clears from the stack and is removed from the view hierarchy. I guess the better solution for me would be using RxSwift or something like this. Anyways, thanks! – Trevor Jun 06 '19 at 08:59
  • DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // redirect rootViewController works here. } – AyAz Jul 27 '19 at 14:08
-1

use this code. no need to remove views manually.

appDelegate.window?.rootViewController = newRootViewController
appDelegate.window?.makeKeyAndVisible()
Parth Bhuva
  • 777
  • 3
  • 21