curt
curt

Reputation: 4592

Executing NSApplicationDelegate Code Before ViewController viewDidLoad

My Swift 3, Xcode 8.2 MacOS app loads several tables through web services calls. Since the tables are used by one or more of my seven view controllers, I placed them in the AppDelegate.

The problem is that the AppDelegate methods applicationWillFinishLaunching and applicationDidFinishLaunching run after the ViewController viewDidLoad methods.

As a result the table views show no data. I was able to get it to work correctly by calling the appDelegate method that loads the data from one of the ViewController viewDidLoad methods. Since any of the ViewControllers could be invoked on application start up, I would have to add the call to all of them and some sort of flagging method to prevent redundant loads.

My question is: where can I place code that will execute prior to the ViewControllers loading? The code loads data into multiple arrays of dictionary. These arrays are in the AppDelegate.

I read up on @NSApplicationMain and replacing it with a main.swift. I assume none of application objects would have been instantiated at that point so I couldn't call their methods and don't think my code would be valid outside of a class.

The pertinent part of my appDelegate:

class AppDelegate: NSObject, NSApplicationDelegate {

var artists: [[String:Any]]? = nil

var dispatchGroup = DispatchGroup() // Create a dispatch group

func getDataFromCatBox(rest: String, loadFunction: @escaping ([[String: Any]]?) -> Void) {
    let domain = "http://catbox.loc/"
    let url = domain + rest
    var request = URLRequest(url: URL(string: url)!)
    request.httpMethod = "Get"
    let session = URLSession.shared
    var json: [[String:Any]]? = nil
    dispatchGroup.enter()
    session.dataTask(with: request) { data, response, err in
        if err != nil {
            print(err!.localizedDescription)
            return
        }
        do {
            json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [[String: Any]]
        }
        catch {
            print(error)
        }
        loadFunction(json)
        self.dispatchGroup.leave()
    }.resume()
}

    func loadArtistTable(array: [[String: Any]]?) {
    artists = array
    }
}

The ViewController code:

override func viewDidLoad() {
    super.viewDidLoad()

    appDelegate = NSApplication.shared().delegate as! AppDelegate
    appDelegate.getDataFromCatBox(rest: "artists.json", loadFunction: appDelegate.loadArtistTable)
    appDelegate.dispatchGroup.wait()
    artistTable.reloadData()
}

The code works in that the TableView is populated when the window appears. While it's not a lot of code, I would have to duplicate across all my View Controllers.

This a prototype. The production version will have 14 tables and invocations.

Upvotes: 3

Views: 729

Answers (2)

curt
curt

Reputation: 4592

I don't think there is any way to do what I want the way it is structured in the question. The ViewController code could be reduced to

appDelegate = NSApplication.shared().delegate as! AppDelegate
appDelegate.getDataFromCatBox(rest: "artists.json", loadFunction: appDelegate.loadArtistTable

by creating a wrapper function in AppDelegate that had the wait in it. It also could contain a flag that indicated that a given table had already been loaded so as not to make a redundant call.

I ended up going with a different approach: I created a super class with singleton subclasses for each table. Now my viewDidLoad method looks like this:

artists.loadTable()  // The sublass
artistTable.reloadData()

If any one comes up with a cleaner solution to the original problem, I'll accept their answer in place of mine.

Upvotes: 1

bhaller
bhaller

Reputation: 2137

I guess my comment should be an answer. So. Why not just make the window containing the table views not be visible on launch? Then in didFinishLaunching, load the table data and then show the window.

Upvotes: 2

Related Questions