Mark Engl Mark Engl - 3 months ago 22
Swift Question

Swift how to set cache tag value in UITableView for multiple sections

I am new to Swift. I am trying to show images in my tableview. I am following this tutorial.

In my

UITableView
, as shown in this tutorial. I am storing images in cache. This works fine if tableview has only one section. How should I set the
forKey
value if I have multiple sections in my tableview?

How to set this value
self.cache.setObject(image!, forKey:indexPath.row)
according to section and row? Thanks!!

This is my code for
UITableView


func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
if(tableView == tableview){
return categoryArray.count
}else{
return 1
}
}

func tableView(tableView : UITableView, titleForHeaderInSection section: Int)->String
{
if(tableView == tableview){
return categoryArray.objectAtIndex(section) as! String
}else{
return ""
}
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if(tableView == tableview){
print("Count = \(myList[section].count)")
return myList[section].count
}
else{
return 5
}

}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
if(tableView == tableview) {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomEventCell

cell.cellBackgroundImg.image = UIImage(named: "placeholder")


if (self.cache.objectForKey(indexPath.row) != nil){
print("Cached image used, no need to download it")
cell.cellBackgroundImg?.image = self.cache.objectForKey(indexPath.row) as? UIImage
}else{

let img_url = myList[indexPath.section].objectAtIndex(indexPath.row).objectForKey("image") as? String

if img_url != nil{
print("Load the image from URL")
let url = NSURL(string: img_url!)!
let request = NSMutableURLRequest(URL:url)
let defaultSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let task = defaultSession.dataTaskWithRequest(request, completionHandler: {(data:NSData?, response:NSURLResponse?, error: NSError?) in

dispatch_async(dispatch_get_main_queue(), { () -> Void in

let image = UIImage(data: data!)
cell.cellBackgroundImg?.image = image
self.cache.setObject(image!, forKey:indexPath.row)
})
})
task.resume()

}
}
return cell
}
}

Answer

The author of the tutorial seems to have made a mistake in choosing to use indexPath.row as the key for their cache. A row number has a very tenuous link to the image; the thing that identifies the image is its URL. By using the row, there will be problems where:

  • Row numbers change (incorrect cache hits)
  • There are multiple sections (just general ickyness as you have found)
  • The same image is used in multiple rows (unnecessary cache misses).

You can easily modify cellForRowAtIndexPath to use the URL as the cache key:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
    var cell = UITableViewCell()
    if(tableView == tableview) {
        let customCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomEventCell
        customCell.cellBackgroundImg.image = UIImage(named: "placeholder")

        if let img_url = myList[indexPath.section].objectAtIndex(indexPath.row).objectForKey("image") as? String {

            if let image = self.cache.objectForKey(img_url) as? UIImage {
                print("Cached image used, no need to download it")
                cell.cellBackgroundImg?.image = image
            } else {
                print("Load the image from URL")
                if let url = NSURL(string: img_url)
                    let request = NSMutableURLRequest(URL:url)
                    let defaultSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
                    let task = defaultSession.dataTaskWithRequest(request, completionHandler: {(data:NSData?, response:NSURLResponse?, error: NSError?) in

                        guard let downloadData= data && error == nil else {
                            print("error downloading image:\(error)"
                            return
                         }
                         dispatch_async(dispatch_get_main_queue(), { () -> Void in
                             if let image = UIImage(data: data) {
                                 customCell.cellBackgroundImg?.image = image
                                 self.cache.setObject(image, forKey:img_url)
                             }  
                          })
                    })
                    task.resume()
                }
           }
        }
        cell = customCell
    }
    return cell  
}

Your code also wasn't very defensive. There was a lot of force-unwrapping which will give you an exception if the data isn't right

Comments