Search code examples
iosswiftuitableviewasynchronousreloaddata

Swift iOS 9 UITableView reloadData after CoreData finished


I try to update my tableview after my REST-call is finished and all entities are stored in CoreData. I tried dispatch_asynch but the tableview doesn't reload when expected. I am using a UIRefreshControl to trigger refreshing. Usually, the correct tableview data is displayed after 2 refreshes. I have no idea why.

I can confirm that my tableview instance IS NOT nil.

@IBAction func refreshTriggered(sender: UIRefreshControl) {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
        self.evMan.retrieveAndSaveEvents(self.pubMan.getAllPublishers())


      dispatch_async(dispatch_get_main_queue()) {
          self.tableView.reloadData()
          self.refreshControl?.endRefreshing()
      }
   }
}

This is my retrieveAndSaveEvents method from my 'EventManager' evMan:

func retrieveAndSaveEvents(forPublishers: [PUBLISHERS]) {

    for publisher in forPublishers {

        let pubId = publisher.id as Int
        let str = "/publishers/\(pubId)/events"

        // resty is an instance of my REST-api wrapper
        self.resty.GET(self.server, route: str, onCompletion: {json in

            let result = json.asArray

            for var i = 0; i < result!.count; i++ {

                if !self.isIDAlreadyInDB(json[i]["id"].asInt!) {
                    let newEv = NSEntityDescription.insertNewObjectForEntityForName("EVENTS", inManagedObjectContext: self.context!) as! EVENTS
                    newEv.headline = json[i]["headline"].asString!
                    newEv.id = json[i]["id"].asInt!
                    newEv.content = json[i]["content"].asString!
                    newEv.ofPublisher = publisher

                    do {
                        try self.context!.save()
                    } catch _ {
                    }

                    print("New Event from \(publisher.name): \(newEv.headline)")
                }
            }
        })
    }
}

FYI, here's my cellForRowAtIndexPath: (I am using a Custom UITableViewCell)

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = self.tableView.dequeueReusableCellWithIdentifier("eventCell") as? EventCell

    let curEv = evMan.getEvents()[indexPath.row]

    cell?.infoLabel.text = curEv.ofPublisher?.name
    cell?.myImageView.image = UIImage(named: "icon.png")
    cell?.detailLabel.text = curEv.headline
    cell?.timeLabel.attributedText = NSAttributedString(string: self.dateFormatter.stringFromDate(curEv.updatedAt))
    cell?.contentView.backgroundColor = UIColor.clearColor()
    cell?.backgroundColor = UIColor(white: 1.0, alpha: 0.5)

    return cell!
}

Here is my REST-wrapper:

class REST: NSObject {

// Basic Auth
let sConfig = ServerConfig()

var username = "rest"
var password = "resttest"

override init() {
    username = sConfig.getUserLogin().user
    password = sConfig.getUserLogin().pass
}

func GET(server: String, route: String, onCompletion: (JSON) -> Void) {
    let route = server+route
    makeHTTPGetRequest(route, onCompletion: { json, err in
        onCompletion(json as JSON)
    })
}

func makeHTTPGetRequest(path: String, onCompletion: ServiceResponse) {
    let request = NSMutableURLRequest(URL: NSURL(string: path)!)

    let loginString = NSString(format: "%@:%@", username, password)
    let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
    let base64LoginString = loginData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions())
    let loginvalue = "Basic " + base64LoginString

    // add Headers
    request.addValue(loginvalue, forHTTPHeaderField: "Authorization")
    request.addValue("application/json", forHTTPHeaderField: "Accept")
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")

    let session = NSURLSession.sharedSession()

    let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
        let json:JSON = JSON(data: data!)
        onCompletion(json, error)
    })
    task.resume()
}
}

Solution

  • @IBAction func refreshTriggered(sender: UIRefreshControl) {
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
            self.evMan.retrieveAndSaveEvents(self.pubMan.getAllPublishers())
        })
    
        dispatch_async(dispatch_get_main_queue()) {
            self.tableView.reloadData()
            self.refreshControl?.endRefreshing()
        }
    }
    

    Now, you cannot guarantee reloadData() happens after self.evMan.retrieveAndSaveEvents(self.pubMan.getAllPublishers()) , because they are not happen in the same queue. And probably, reloadData() happens before retrieve.

    Option 1:

    You should put the block :

       dispatch_async(dispatch_get_main_queue()) {
            self.tableView.reloadData()
            self.refreshControl?.endRefreshing()
        }
    

    at the end of func retrieveAndSaveEvents(self.pubMan.getAllPublishers())

    Option 2 :

     @IBAction func refreshTriggered(sender: UIRefreshControl) {
    
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
                self.evMan.retrieveAndSaveEvents(self.pubMan.getAllPublishers())
                dispatch_async(dispatch_get_main_queue()) {
                    self.tableView.reloadData()
                    self.refreshControl?.endRefreshing()
                }
            })
    
        }
    

    EDIT:

    I did not see there is another queue in retrieveAndSaveEvents

    So, put the

       dispatch_async(dispatch_get_main_queue()) {
            self.tableView.reloadData()
            self.refreshControl?.endRefreshing()
        }
    

    at the end of:

    self.resty.GET(self.server, route: str, onCompletion: {json in
            let result = json.asArray
    
            for var i = 0; i < result!.count; i++ {
    
                if !self.isIDAlreadyInDB(json[i]["id"].asInt!) {
                    let newEv = NSEntityDescription.insertNewObjectForEntityForName("EVENTS", inManagedObjectContext: self.context!) as! EVENTS
                    newEv.headline = json[i]["headline"].asString!
                    newEv.id = json[i]["id"].asInt!
                    newEv.content = json[i]["content"].asString!
                    newEv.ofPublisher = publisher
    
                    do {
                        try self.context!.save()
                    } catch _ {
                    }
    
                    print("New Event from \(publisher.name): \(newEv.headline)")
                }
            }
        })