1

I realize that there are several similar questions to this one, and I have indeed checked a lot of them. However, the ones I checked were either in the wrong language, or not what I was looking for, therefore I'm asking a new question. For reference, I'm using XCode 7.3.1 in Swift 2.

There is one feature of my new iOS app that will search the internet for data, and display the results by populating a UITableView. My problem is that it displays pictures in each cell, which makes the scrolling very annoying.

My ideal solution would be to have a default picture display, and while allowing the user to continue scrolling, it will download in the background. Then, once it has retrieved it, it will replace the default. I saw some previous answers on how to do this, but none have worked for me.

P.S. If you see another question that solves this problem WITH SWIFT, post the url and I'll delete this one.

Here is the code I'm using currently (with the lag):

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {  // Creates each cell, by parsing through the data received from the Employees array which we returned from the database
    if (Employees.count == 0)
    {
        let cell = self.ResultsTable.dequeueReusableCellWithIdentifier("NORESULT", forIndexPath: indexPath) as! TruckSearchNRTableViewCell

        return cell
    }

    else {
        let cell = self.ResultsTable.dequeueReusableCellWithIdentifier("RESULT", forIndexPath: indexPath) as! TruckSearchTableViewCell

            cell.companyLabel.text = Employees[indexPath.row]["CompanyName"] as? String
            cell.nameLabel.text = Employees[indexPath.row]["EmployeeName"] as? String
        if let mobile = Employees[indexPath.row]["PhoneNumber"] as? String {
            if (mobile == "")
            {
                cell.mobileLabel.hidden = true
                cell.mobileTitle.hidden = true
            } else {
                cell.mobileTitle.hidden = false
                cell.mobileLabel.hidden = false
            }
            let phonenumber = mobile.stringByReplacingOccurrencesOfString("[^0-9]", withString: "", options: NSStringCompareOptions.RegularExpressionSearch, range:nil);
            cell.phoneNumber.text = phonenumber
        }
            cell.titleLabel.text = Employees[indexPath.row]["JobTitle"] as? String
            cell.truckLabel.text = Employees[indexPath.row]["TruckNumber"] as? String
            cell.supervisorLabel.text = Employees[indexPath.row]["SupervisorName"] as? String
        if (Employees[indexPath.row]["Synced"] as? Int) == 0 {
            turnRed(cell)
        } else {
            turnBlack(cell)
        }
        if let ePhoto = Employees[indexPath.row]["PicLocation"] as? String {  // Save complete URL of picture location, and save it to the table

            let url = NSURL(string: "https://exampleurl.com/\(ePhoto)")!
            if let data = NSData(contentsOfURL: url){
                let myImage = UIImage(data: data)
                cell.employeePhoto.image = myImage
            }
            else
            {
                cell.employeePhoto.image = UIImage(named: "person-generic")
            }

        }

        return cell
    }
}
JFed-9
  • 297
  • 3
  • 17
  • 2
    Please take a look at [this](http://stackoverflow.com/a/26147814/1611876) solution. It's in ObjC but translating it to Swift should be trivial task. Also you would like to have some cache mechanism to avoid downloading every image every time. [SDWebImage](https://github.com/rs/SDWebImage) is good solution for what you want to achieve and you'll have the **background downloading / caching / placeholder image** ready for use. – vhristoskov Jun 26 '16 at 00:16
  • I'll look into it when I get the chance, thanks! – JFed-9 Jun 26 '16 at 00:36

1 Answers1

6

The problem is that you are retrieving the picture on the main thread, which is blocking it. All UI operations, including scrolling, are performed on the main thread so that is why it is choppy at the moment.

To solve this, instead of:

if let data = NSData(contentsOfURL: url){

    let myImage = UIImage(data: data)
    cell.employeePhoto.image = myImage

} else {

    cell.employeePhoto.image = UIImage(named: "person-generic")

}

Put the image request on a background thread and then set the image on the main thread again.

Eg

let qos = QOS_CLASS_USER_INITIATED

dispatch_async(dispatch_get_global_queue(qos,  0), {

   if let data = NSData(contentsOfURL: url){

       let myImage = UIImage(data: data)

       dispatch_async(dispatch_get_main_queue(), {
           cell.employeePhoto.image = myImage
       })

   } else {

       dispatch_async(dispatch_get_main_queue(), {
           cell.employeePhoto.image = UIImage(named: "person-generic")
       })

   }

})

alternatively you can use an asynchronous url session to get the data like so:

let request: NSURLRequest = NSURLRequest(URL: url)
let mainQueue = NSOperationQueue.mainQueue()

NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in

    if error == nil {

        let myImage = UIImage(data: data)

        dispatch_async(dispatch_get_main_queue(), {
            cell.employeePhoto.image = myImage  
        })

    } else {

       dispatch_async(dispatch_get_main_queue(), {
           cell.employeePhoto.image = UIImage(named: "person-generic")
       })

    }

})
Olivier Wilkinson
  • 2,836
  • 15
  • 17
  • It's still pretty jumpy, could it not be returning the cell fast enough? – JFed-9 Jun 26 '16 at 00:34
  • sorry i was writing that on my phone, missed the else statement. try my edited code and tell me whether that works. judging from your code it shouldn't be a processing speed issue – Olivier Wilkinson Jun 26 '16 at 00:51
  • You also need to access `image.CGImage` on the background thread to avoid decoding the image data on the main thread. Furthermore, since cells get reused, this answer will cause incorrect images to appear momentarily. – rob mayoff Jun 26 '16 at 01:08
  • where did you see image.CGImage ? I can't find it :/ – Olivier Wilkinson Jun 26 '16 at 01:13
  • @robmayoff also cell reuse isn't part of the question. – Olivier Wilkinson Jun 26 '16 at 01:15
  • It's explained in many places, for example http://stackoverflow.com/a/26186907/77567 – rob mayoff Jun 26 '16 at 01:20
  • that's the only place I can find this trick, and it seems hacky to me, not at all how the language was intended to be used. – Olivier Wilkinson Jun 26 '16 at 01:28
  • It's not part of the language, it's part of the API, and the `CGImage` property is documented to force the image data to be loaded. You can find lots of relevant hits by googling “force uiimage decode” or “dispatch_async cgimage”, though many go through more hoops than necessary by actually drawing the image to a context on a background thread. Anyway, you're welcome to decide whether you want a "hack" or whether you want smooth scrolling. – rob mayoff Jun 26 '16 at 04:18
  • As for cell reuse: the question involves putting an image in a table view cell. It's irrelevant whether the question mentions cell reuse, because (unless you are using static cells) your table view reuses its cells even if you don't mention it in your stack overflow question. The problem I mentioned, and a solution for it, in about the last ten minutes of [WWDC 2012 Session 211: Building Concurrent User Interfaces on iOS](https://developer.apple.com/videos/play/wwdc2012/211/). – rob mayoff Jun 26 '16 at 04:20
  • Fair dues, apologies for the aggro last night! I had been on a night out and was very tired and grumpy it seems. – Olivier Wilkinson Jun 26 '16 at 08:53
  • Makes sense, I'll look more into forced decoding. And as for the cell reuse I was of a mind that you answer the question at hand and they continue googling for subsequent problems, but then I had a quick look at your answers... Good lord they can be thorough! Very impressive, I'm going to endeavour to emulate that. – Olivier Wilkinson Jun 26 '16 at 08:56
  • Sadly, neither of those snippets of code provided smooth scrolling for me, but I'll start down the path you guys have been talking about. Thanks for your help! – JFed-9 Jun 27 '16 at 21:28
  • I will however mark yours as the correct answer, since you put in a lot of work, and your code does seem like it should work. It must be some bug elsewhere in my code that prevents success. – JFed-9 Jun 27 '16 at 21:29