pomo_mondreganto
pomo_mondreganto

Reputation: 2074

Preload ViewController on application start

I've a heavy UIViewController, which loads multiple images in viewDidLoad (so it's called once, on the first controller access only). It takes 5-10 seconds for it to load, which I want to reduce, preloading it from somewhere on application start.

I've done my research and tried accessing the view attribute of the controller for it to load in didFinishLaunchingWithOptions method of AppDelegate, and it actually did (viewDidLoad was called), but when I one the controller itself, viewDidLoad is called again and all the images are loaded again too.

Current code example is the following:

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

    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let vc = storyboard.instantiateViewController(withIdentifier: "modeDescriptionViewController")
    let _ = vc.view  // viewDidLoad is called here

    return true
}

How can I preload a huge UIViewController so it doesn't take so much to load when opened? Data it's holding is completely static, so any solution would apply.

Upvotes: 2

Views: 945

Answers (3)

Cristik
Cristik

Reputation: 32914

wvteijlingen's answer should be the way to go, extracting the heavy (business) logic from the controller into a dedicated class is the optimal solution. However if refactoring would be complicated, then you can follow the approach described below.


The controller instance you create in didFinishLaunchingWithOptions is lost once the method exits, so its view and the contents loaded by the view are discarded. If you want to reuse that instance, you can store it into a property of AppDelegate:

var modeDescriptionViewController: UIViewController!

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

    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    modeDescriptionViewController = storyboard.instantiateViewController(withIdentifier: "modeDescriptionViewController")
    // loadViewIfNeeded() is cleaner
    modeDescriptionViewController.loadViewIfNeeded()
    return true
}

And from this point on you can access the view controller when needed: UIApplication.shared.delegate as! AppDelegate).modeDescriptionViewController.

Upvotes: 1

Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 120082

Although your solution is not going to help you to achieve faster load and you MUST make your heavy works async :

Your explained problem is when you instantiateViewController it creates a new view controller that is completely different from the one you will see later. So instead of that, you must get it. One way you can do that is to access it from the main window in appDelegate:

let controller = window?.rootViewController as? modeDescriptionViewController

Then you can force it to load it’s view (not recommended at all)

Controller.loadView()

And then it calls viewDidLoad automatically

Tip: If window is not loaded, you can call makeKeyAndVisible() on it.

Upvotes: 0

Rengers
Rengers

Reputation: 15238

I would suggest extracting all the loading logic from the controller. Create a DataLoader class (or struct) that does the loading and stores the loaded data.

Instantiate a DataLoader in your AppDelegate and instruct it to load the data, decoupled from the view controller. Then pass this instance to the controller, so the controller can access the data from the loader.

You will probably need to implement some state tracking to make sure that when you ask the loader for the data, it will either begin loading, or wait if loading is already in progress.

A a plus, you can also implement loading in a background queue so it doesn't block the UI.

Upvotes: 3

Related Questions