user616076 user616076 - 4 months ago 16
iOS Question

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?

Answer

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")
}