7

I'm trying to remove all storyboards from our iOS app as they are a huge mess when working in a team with Git.

I'm now setting the initial ViewController in AppDelegate's application(_:didFinishLaunchingWithOptions:) method:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    window = UIWindow(frame: UIScreen.main.bounds)
    let launchViewController = LaunchView()
    window!.rootViewController = launchViewController
    window!.makeKeyAndVisible()

    [...]

    return true
}

LaunchView is a simple view controller responsible of routing the user to login or main screen depending if he/she is logged in.

Before this, LaunchView was set as initial in Main.storyboard.

I already removed these lines from Info.plist file:

<key>UIMainStoryboardFile</key>
<string>Main</string>

Everything is working fine, except when we leave the app in background for a couple hours without force-quitting it (I'm not sure how much time is needed to reproduce this) and then bring the app back to foreground, an all-black screen is shown, as if the root controller disappeared. And we have to kill the app and reopen it again in order to use it.

This is driving me crazy because it is really hard to reproduce, since if you leave the app in background only for a few minutes it works fine. I'm suspecting it has something to do with the app suspended state, but I'm not really sure.

Do you know where the problem might be?

Thank you!

EDIT

Maybe this has something to do with the problem: On the LaunchView, I'm sending the user to the main screen (if he is logged in) with the following code:

guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }

let rootVC = UIStoryboard.main.rootViewController
if let snapshot = appDelegate.window?.snapshotView(afterScreenUpdates: true) {

    rootVC.view.addSubview(snapshot);
    appDelegate.window?.rootViewController = rootVC

    UIView.transition(with: snapshot, duration: 0.4, options: .transitionCrossDissolve, animations: {
        snapshot.layer.opacity = 0;
    }, completion: { (status) in
        snapshot.removeFromSuperview()
    })
}
else {
    appDelegate.window?.rootViewController = rootVC
}

Is it possible that iOS is removing rootViewController at some point?

Rouger
  • 543
  • 5
  • 13
  • This issue in simulator or device? Is it a universal application? – user1376400 Feb 28 '19 at 16:29
  • @user1376400 This occurs on a device, I didn't try it on the simulator. The app is for iPhone only – Rouger Feb 28 '19 at 16:42
  • do you have LaunchScreen ? – a.masri Feb 28 '19 at 16:43
  • @a.masri yes, it is storyboard named `Launch.storyboard`. This is selected as Launch Screen File (`UILaunchStoryboardName` key in Info.plist). – Rouger Feb 28 '19 at 16:47
  • Any other storyboard in the app? I mean in info.plist – user1376400 Feb 28 '19 at 16:50
  • please share your code for appDidEnterBackground – Julian Silvestri Feb 28 '19 at 16:52
  • @user1376400 Yes, a lot of them (12 I think). Also, I still have `Main.storyboard` as I have the main view controller in there and didn't refactor it yet. – Rouger Feb 28 '19 at 16:53
  • have you tried this? definesPresentationContext = true in viewDidLoad() – Julian Silvestri Feb 28 '19 at 16:55
  • @JulianSilvestri In `applicationDidEnterBackground` I'm posting an `NSNotification' to default notification center, and using it in a couple observers to stop some processes running in background threads. Nothing UI-related. – Rouger Feb 28 '19 at 16:58
  • You have to add UIMainStoryboardFile Main this in info plist – user1376400 Feb 28 '19 at 16:58
  • @JulianSilvestri no, I did not try `definesPresentationContext = true`, but I will and let you know, thanks! – Rouger Feb 28 '19 at 17:00
  • @user1376400 I don't think so, because I'm not using `Main.storyboard` as the Main Interface, I'm only using it as a regular storyboard. – Rouger Feb 28 '19 at 17:04
  • Share the code in AppDelegate – user1376400 Feb 28 '19 at 17:08
  • Without seeing what happens when app goes to background or returns from background, it feels like guessing in the dark. Could you share the AppDelegate code? If too much or too messy, focus on app going to background and app coming to foreground methods. – Alex Ioja-Yang Mar 01 '19 at 08:14
  • @user1376400 @AlexIoja-Yang sorry, but the AppDelegate is huge. The app state related methods I only do something in are `applicationDidBecomeActive` and `applicationDidEnterBackground`. Both methods are sending an NSNotification to the Default center to inform interested observers, and these observers don't execute any UI-related code. I don't think this part has nothing to do with the problem, because it is doing exactly the same than before (when the problem didn't exist) – Rouger Mar 01 '19 at 08:44
  • please share the complete code over this link "https://justpaste.it/" and share the link here. – Babul Mar 01 '19 at 10:15
  • @Babul sorry but this won't be possible. I don't own neither this code nor its rights. I can't share the whole code. – Rouger Mar 01 '19 at 12:10
  • @JulianSilvestri setting `definesPresentationContext = true` on `viewDidLoad()` didn't work :( – Rouger Mar 04 '19 at 08:46

1 Answers1

1

Turns out the problem was related to the transition effect involving a snapshot when swaping the window's rootViewController.

I removed the transition effect and simply changed the rootViewController. Now it's working fine!

In code, I changed this:

guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }

let rootVC = UIStoryboard.main.rootViewController
if let snapshot = appDelegate.window?.snapshotView(afterScreenUpdates: true) {

    rootVC.view.addSubview(snapshot);
    appDelegate.window?.rootViewController = rootVC

    UIView.transition(with: snapshot, duration: 0.4, options: .transitionCrossDissolve, animations: {
        snapshot.layer.opacity = 0;
    }, completion: { (status) in
        snapshot.removeFromSuperview()
    })
}
else {
    appDelegate.window?.rootViewController = rootVC
}

To this:

guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let rootVC = UIStoryboard.main.rootViewController
appDelegate.window?.rootViewController = rootVC

Thank you all for your help! I hope this answer is useful to somebody else.

Rouger
  • 543
  • 5
  • 13
  • You should not be changing the root view controller at all. – matt Mar 15 '19 at 11:29
  • @matt I'm changing it to avoid stacking all the other view controllers to my `LaunchViewController`. What should I do instead? – Rouger Mar 15 '19 at 11:45
  • Have one eternal root view controller that is just a parent view controller and change its child. – matt Mar 15 '19 at 15:09
  • @matt Why? Is there any documentation or guidelines that says is a bad practice to change the rootViewController? – Rouger Mar 15 '19 at 17:06
  • Well one obvious thing is that you could then have your transition that you just had to eliminate. Thus you would solve the problem the right way. – matt Mar 15 '19 at 17:35
  • @matt Ok, but I don't think that deserves a downvote. I accept that as a suggestion, although I think it makes no sense keeping that VC (which won't be used or seen anymore) in the stack. – Rouger Mar 18 '19 at 11:24
  • On the contrary an unseen master parent view controller is a correct and standard technique straight from Apple Inc. – matt Mar 18 '19 at 14:49
  • See https://stackoverflow.com/questions/41144523/swap-rootviewcontroller-with-animation/41144993#41144993 – matt Mar 19 '19 at 09:25