1
votes

I have an app that downloads some information from a URL (ie. author name, story title, and the cover image). I'm able to download and parse the JSON from the server properly, but I'm stuck at one point.

The app consists of one View Controller (called ViewController.swift) and one class file (called GetStories.swift). Once the app has finished download and parsing the JSON from the server, I want the table view in the View Controller to reload itself (self.tableView.reloadData()).

I've set up a chain of completion blocks in GetStories.swift that accomplish the following steps in order: 1) Download the JSON 2) Parse the JSON 3) Save it to disk

func updateUI(){
    saveDownloadedAndParsedJSONToDisk {
    }
}
func saveDownloadedAndParsedJSONToDisk(completionHandler: @escaping RefreshTableView){
    parseJSON {
        self.saveDataToJSON()
        completionHandler()
    }
}
func parseJSON(completionHandler: @escaping ReadyToSave){
    downloadJSON { jsonPayload, error in
        do {
            if let data = jsonPayload {
                self.stories = try JSONDecoder().decode(Stories.self, from: data)
                if let stories = self.stories {
                    self.stories = stories
                    completionHandler()
                } else {
                    print("An error occurred while decoding JSON.")
                }
            } else if let error = error {
                print("Error retrieving data: \(error)")
            }
        } catch {
            print(error.localizedDescription)
        }
    }
}
func downloadJSON(completionHandler: @escaping NetworkResponse){
    let storiesAPIURL = URL(string: "\(wattpadAPIURL)")
    var wattpadAPIRequest = URLRequest(url: storiesAPIURL!)
    wattpadAPIRequest.httpMethod = "GET"
    let session = URLSession.shared
    let dataTask = session.dataTask(with: wattpadAPIRequest) { (data : Data?, response : URLResponse?, error : Error?) in
        if let data = data {
            completionHandler(data, nil)
        } else if let error = error {
            completionHandler(nil, error)
            print(error.localizedDescription)
        }
    }
    dataTask.resume()
}

In ViewController.swift, I am calling updateUI(). Then, I'm calling the delegate method in the protocol:

self.storyResults?.delegate?.didFinishFetchingAndParsingData(finished: true)

The delegate method is doing this:

    func didFinishFetchingAndParsingData(finished: Bool) {
    guard finished else {
        return
    }
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        self.tableView.reloadData()
        SVProgressHUD.dismiss()
    }
}

So, as you see, I'm using a 0.5 second delay to reload the table view because I'm not sure how to tell when exactly the downloading, parsing and saving has all fully finished.

If I don't use the delay of 0.5 seconds in the delegate method, the table view gets reloaded at an inappropriate time and there are no results displayed as a result. So, executing the reload this way doesn't work:

        DispatchQueue.main.async {
        self.tableView.reloadData()
    }

What is the proper way to do this?

Thanks in advance!

1

1 Answers

1
votes

Move the delegate call to the completionHandler inside updateUI, then it will be called at the right moment. Right now you have an empty completionHandler there.

func updateUI() {
    saveDownloadedAndParsedJSONToDisk {
        DispatchQueue.main.async {
            self.storyResults?.delegate?.didFinishFetchingAndParsingData(finished: true)
        }
    }
}
...

func didFinishFetchingAndParsingData(finished: Bool) {
    guard finished else {
        return
    }

    self.tableView.reloadData()
    SVProgressHUD.dismiss()
}