Search code examples
iosswiftweb-servicesuitableviewnsurlsessiondownloadtask

What is the time delay between getting data and loading to UITableView


I'm loading my UITableView from an Api call but although the data is retrieved fairly quickly, there is a significant time delay before it is loaded into the table. The code used is below

import UIKit

class TrackingInfoController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet var table : UITableView?
@IBOutlet var indicator : UIActivityIndicatorView?
@IBOutlet var spinnerView : UIView?

var tableArrayList = Array<TableData>()

struct TableData
{
    var dateStr:String = ""
    var nameStr:String = ""
    var codeStr:String = ""
    var regionStr:String = ""

    init(){}
}

override func viewDidLoad() {
    super.viewDidLoad()

    table!.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
    spinnerView?.hidden = false
    indicator?.bringSubviewToFront(spinnerView!)
    indicator!.startAnimating()
    downloadIncidents()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

@IBAction func BackToMain() {
    performSegueWithIdentifier("SearchToMainSegue", sender: nil)
}

//#pragma mark - Table view data source

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1     //BreakPoint 2
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return tableArrayList.count;
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomTableViewCell

    cell.incidentDate.text = tableArrayList[indexPath.row].dateStr
    cell.incidentText.text = tableArrayList[indexPath.row].nameStr
    cell.incidentCode.text = tableArrayList[indexPath.row].codeStr
    cell.incidentLoctn.text = tableArrayList[indexPath.row].regionStr

    return cell     //BreakPoint 4
}

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
    AppDelegate.myGlobalVars.gIncName = tableArrayList[indexPath.row].nameStr
    AppDelegate.myGlobalVars.gIncDMA = tableArrayList[indexPath.row].codeStr

    performSegueWithIdentifier("SearchResultsToDetailSegue", sender: nil)
}

func alertView(msg: String) {
    let dialog = UIAlertController(title: "Warning",
                                   message: msg,
                                   preferredStyle: UIAlertControllerStyle.Alert)
    dialog.addAction(UIAlertAction(title: "Ok", style: .Default, handler: nil))
    presentViewController(dialog,
                          animated: false,
                          completion: nil)
}

func downloadIncidents()
{
    var event = AppDelegate.myGlobalVars.gIncName
    var DMA = AppDelegate.myGlobalVars.gIncDMA
    if event == "Enter Event Name" {
        event = ""
    }
    if DMA == "Enter DMA" {
        DMA = ""
    }
    let request = NSMutableURLRequest(URL: NSURL(string: "http://incident-tracker-api-uat.herokuapp.com/mobile/events?name=" + event)!,
                                      cachePolicy: .UseProtocolCachePolicy,
                                      timeoutInterval: 10.0)
    request.HTTPMethod = "GET"
    let session = NSURLSession.sharedSession()
    let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
        if error != nil {
            self.alertView("Error - " + error!.localizedDescription)
        }
        else {
            do {
                var incidentList: TableData
                if let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments) as? Array<Dictionary<String, AnyObject>> {
                    for item in json {
                        if let dict = item as? Dictionary<String, AnyObject> {
                            incidentList = TableData()
                            if let nameStr = dict["name"] as? String {
                                incidentList.nameStr = nameStr
                            }
                            if let codeStr = dict["dma"] as? String {
                                incidentList.codeStr = codeStr
                            }
                            if let dateStr = dict["supplyOutageStart"] as? String {
                                let tmpStr = dateStr
                                let index = tmpStr.startIndex.advancedBy(10)
                                incidentList.dateStr = tmpStr.substringToIndex(index)
                            }
                            if let regionStr = dict["region"] as? String {
                                incidentList.regionStr = regionStr
                            }
                            self.tableArrayList.append(incidentList)
                        }
                    }
                    self.spinnerView?.hidden = true
                    self.indicator?.stopAnimating()
                    self.table?.reloadData()     //BreakPoint 3
                }
            }catch let err as NSError
            {
                self.alertView("Error - " + err.localizedDescription)
            }
        }
    })
    task.resume()      //BreakPoint 1
}

When the class is run, it hits BreakPoint 1 first and then hits BreakPoint 2 and then quickly goes to BreakPoint 3, it then goes to BreakPoint 2 once more. Then there is a delay of about 20 to 30 seconds before it hits Breakpoint 4 in cellForRowAtIndexPath() and the data is loaded into the UITableView. The view is displayed quickly afterwards.

The data is retrieved quite quickly from the Web Service so why is there a significant delay before the data is then loaded into the tableView? Is there a need to thread the Web Service method?


Solution

  • You are getting server response in a background thread so you need to call the reloadData() function on the UI thread. I am suspecting that the wait time can vary depending on whether you interact with the app, which effectively calls the UI thread, and that's when the table actually displays the new data.

    In a nutshell, you need to wrap the self.table?.reloadData() //BreakPoint 3 with

    dispatch_async(dispatch_get_main_queue()) {
        // update some UI
    }
    

    The final result would be

    Pre Swift 3.0

    dispatch_async(dispatch_get_main_queue()) {
        self.table?.reloadData()
    }
    

    Post Swift 3.0

    DispatchQueue.main.async {
        print("This is run on the main queue, after the previous code in outer block")
    }