AboutContact

Swift cache: how to download and cache data in iOS

You'll learn how to cache data in swift by developing a local cache based on core data for the downloaded data. You'll see through an example how to define a custom Table View for iOS using Swift, how to load the json data that define the images urls and descriptions, how to load images from url and how to store image in core data. You'll see how to read the stored cached data from core data entity and display the images without dowloading them again.


table view image

 

Add the UitableView  component

Create a new iOS single view applicaton project. Be sure to check that the use core data functionality is on.

Swift: Turn core data on

Let's now define the layout. Open the Main.storyboard (you can turn the use size classes functionality off) and put a UITableView component. Put inside the UITableView component the UITableViewCell.
With the previously added cell selected, set the reuse identifier (attribute inspector menu) to cell.

swift. table view controller

 

Open now the assistant editor and link the table view component to the ViewController class. Call the variable tableview.

swift: assistant editor

 

Coding Part

For the Json data I've prepared for you a simple data structure. You can find it here. It is a simple array of objects. If you want, you can use this easy tool to explore the structure in a readable format.

Open now the ViewController and let's define the required variables.

    var json_data_url = "https://www.kaleidosblog.com/tutorial/json_table_view_images.json"
    var image_base_url = "https://www.kaleidosblog.com/tutorial/"
    var TableData:Array< datastruct > = Array < datastruct >()    
    struct datastruct
    {
        var imageurl:String?
        var description:String?
        var image:UIImage? = nil

        init(add: NSDictionary)
        {
            imageurl = add["url"] as? String
            description = add["description"] as? String
        }
    }
    @IBOutlet var tableview: UITableView!

The TableData array will contain the json extracted list. Moreover it has also a UIImage variable (called image) that is initialized to nil. This variable will contain the image data, when available. This varialbe is used to define a local cache that will store for a given session the image data. In this way, when you scroll down the table  the cell is reused for another image. As soon as you scroll up again the image is in the cache and will not be downloaded again.

Let's define now the Table view behaviour. Be also sure that the ViewController class implements also the UITableViewDataSource and the UITableViewDelegate

 override func viewDidLoad() {
        super.viewDidLoad()
        
        tableview.dataSource = self
        tableview.delegate = self
        
        get_data_from_url(json_data_url)
    }
    
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
    {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
        
        var data = TableData[indexPath.row]
        
        
        cell.textLabel?.text = data.description
        
        if (data.image == nil)
        {
            cell.imageView?.image = UIImage(named:"image.jpg")
            load_image(image_base_url + data.imageurl!, imageview: cell.imageView!, index: indexPath.row)
        }
        else
        {
             cell.imageView?.image = TableData[indexPath.row].image
        }
        
        return cell
        
    }
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        return TableData.count
    }

At this point we have the table view ready to display the data in a proper way, but we need now the data. 

Define the Core Data Entity

Let's now define the Core data entity that will store the images in a permanent way. It means that if you close the app and you open it again, the data is still here. In this way the core data will be used for caching the download images.

Open the xcdatamodeld and insert a new entity. Call it Images and add two attributes:  image - Binary Data and index - type integer 16.

core data entity 

Downloading and extrating the data from JSON

Turn now back to the ViewController class. We'll define the functions that download the JSON data in an asynchronous way, extract the JSON data, fill the array with the data and refresh the table. After the JSON data are extracted, the read function is called. This function will be implemented in the next step and will read from the Core Data entity the stored data and will fill the TableData array with the cached images data.

    func get_data_from_url(url:String)
    {
        let httpMethod = "GET"
        let timeout = 15
        let url = NSURL(string: url)
        let urlRequest = NSMutableURLRequest(URL: url!,
            cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
            timeoutInterval: 15.0)
        let queue = NSOperationQueue()
        NSURLConnection.sendAsynchronousRequest(
            urlRequest,
            queue: queue,
            completionHandler: {(response: NSURLResponse!,
                data: NSData!,
                error: NSError!) in
                if data.length > 0 && error == nil{
                    let json = NSString(data: data, encoding: NSASCIIStringEncoding)
                    self.extract_json(json!)
                }else if data.length == 0 && error == nil{
                    println("Nothing was downloaded")
                } else if error != nil{
                    println("Error happened = \(error)")
                }
            }
        )
    }
    

    func extract_json(data:NSString)
    {
        var parseError: NSError?
        let jsonData:NSData = data.dataUsingEncoding(NSASCIIStringEncoding)!
        let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: &parseError)
        if (parseError == nil)
        {
            if let list = json as? NSArray
            {
                for (var i = 0; i < list.count ; i++ )
                {
                    if let data_block = list[i] as? NSDictionary
                    {
                    
                        TableData.append(datastruct(add: data_block))
                    }
                }
                read()
                do_table_refresh()
                
            }
            
        }
    }
    
    
    
    
    func do_table_refresh()
    {
        dispatch_async(dispatch_get_main_queue(), {
            self.tableview.reloadData()
            return
        })
    }
 

Download the image and update the UITableViewCell

Let's now define the function that will download the image from url, update the TableData local cache (for that session), update the Core data (for the permanent cache) and finally update the UITableViewCell image.

 func load_image(urlString:String, imageview:UIImageView, index:NSInteger)
    {
        
        var imgURL: NSURL = NSURL(string: urlString)!
        let request: NSURLRequest = NSURLRequest(URL: imgURL)
        NSURLConnection.sendAsynchronousRequest(
            request, queue: NSOperationQueue.mainQueue(),
            completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
                if error == nil {
                    self.TableData[index].image = UIImage(data: data)
                    self.save(index,image: self.TableData[index].image!)

                    imageview.image = self.TableData[index].image
                    
                }
        })
        
    }
 

Store image in Core Data: Write and read the data cache

Let's now define the functions that will read and store the data in core data. Be sure to import the CoreData library. The UIImageJPEGRepresentation function let us convert the jpg image to a data format that can be stored and cached in core data.

    func read()
    {
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        let managedContext = appDelegate.managedObjectContext!
        let fetchRequest = NSFetchRequest(entityName: "Images")
        
        var error: NSError?
        let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error)
            as! [NSManagedObject]?
        
        if let results = fetchedResults
        {
            for (var i=0; i < results.count; i++)
            {
                let single_result = results[i]
                let index = single_result.valueForKey("index") as! NSInteger
                let img: NSData? = single_result.valueForKey("image") as? NSData
                
                TableData[index].image = UIImage(data: img!)
                
            }
        }
        
    }
    
    func save(id:Int,image:UIImage)
    {
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        let managedContext = appDelegate.managedObjectContext!
        
        let entity = NSEntityDescription.entityForName("Images",
            inManagedObjectContext: managedContext)
        let options = NSManagedObject(entity: entity!,
            insertIntoManagedObjectContext:managedContext)
        
        var newImageData = UIImageJPEGRepresentation(image,1)
        
        options.setValue(id, forKey: "index")
        options.setValue(newImageData, forKey: "image")
        
        var error: NSError?
        managedContext.save(&error)

    }
 
The read function let you read all the image you've stored in core data. The save function will store image in core data.

Download the example.
UPDATE: Download the example for swift 2 from here

If you enjoyed this tutorial, share it. You can fine more Swift tutorial here.
For any question, be free to write a comment below.

 

 

 

Leave a comment








Comments


- 06 April 2017 at 11:42
Hi Great tutotial. I am struggling with the conversion to Swift3 / iOS10 Can you please update the tutorial? Thanks!
- 29 June 2016 at 02:00
Hi Thanks for the great tutorial. I'm wondering where do you set the exact position of the image and the label?
Andrea - 29 June 2016 at 08:57
Hi, in this tutorial I've used the default iOS table cell, that containts the image and the label in that default position. However you can define your custom cell. Here you can find a tutorial about this: https://www.kaleidosblog.com/uitableview-custom-cell-how-to-define-a-custom-cell-with-swift-in-ios
- 29 April 2016 at 13:43
Hi Andrea, great tutorial, it is really helpful tutorials like this one, I have a question, how to make this app work offline, always using core data ? Thank you again
Andrea - 29 April 2016 at 17:40
Hi, thank you for your comment. In this tutorial I've applied core data only for the images. This app requires to be online to download the external json data structure, and only then can display the images that are inside core data - this will reduce the required bandwith to avoid downloading every time the images. In order to allow this app to working also offline, you may to store inside core data also the json data. You can Have somthing link the url and the data, Every time you have to download some data, check the offline version inside core data and directly display it.
Zak - 29 April 2016 at 23:55
I misunderstood, I download the tutorial, it does not work in offline mode, my question is, is that this tutorial works in offline mode ?
Andrea - 30 April 2016 at 18:53
No, the app-example requires to be online to download the json data. Only the images binary is cached in core data.
- 27 April 2016 at 09:19
Awesome tutorial :-) Playing around with it to adjust for my own app. I have created my own JSON file, but I don't want to name the images: image1, image2 etc but would rather use their original names. In the func tableview, you loop through and used named:"image.jpg" (see code below). How would i change this if the image names were different? if (data.image == nil) { cell.imageView?.image = UIImage(named:"image.jpg") load_image(image_base_url + data.imageurl!, imageview: cell.imageView!, index: indexPath.row) } else { cell.imageView?.image = TableData[indexPath.row].image } return cell
Andrea - 27 April 2016 at 17:03
Hi, thank you for your comment. You can change the name of the images that will be downloaded as you want. The image.jpg image is just a blue placeholder that is displayed when the image has not been downloaded yet. Everything else is defined by the json data and is downloaded with the load_image function.
- 26 March 2016 at 15:26
Is it possible to update online refresh
Andrea - 26 March 2016 at 21:35
Hi, do you mean if it is possible to add the pull to refresh functionality to refresh the data?
- 06 February 2016 at 23:59
Thank you so much for this! This is so simple and easy to follow!
Andrea - 07 February 2016 at 19:19
You are welcome!
- 03 February 2016 at 19:58
This will result in an ever increasing CoreData, a proper cache design pattern should also take care of clearing the data... and loading images from CoreData, not using the network everytime.
Andrea - 03 February 2016 at 20:32
Hi, thank you for your comment. In this example I've covered only the part of caching the images and not of removing them when unused. Moreover the images (not the json data) are loaded from coredata as you can seen from the current example without using the network.
- 25 November 2015 at 19:30
Shouldn't the images still load when there is no network connection? They don't. Great tutorial, but seems that if they not loading from core data without network connection, what is the point?
Andrea - 25 November 2015 at 20:44
Hi, thank you for your comment. This example store only the images in cache, not the data with the images. Images can waste a lot of bandwith, while json data are few kb. So if you turn off the connection, no data for the table will be downloaded. Be free to edit this example to extend it also for the json data.
- 05 October 2015 at 19:18
This is my info.list NSAppTransportSecurity NSAllowsArbitraryLoads CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1
Alex - 05 October 2015 at 19:19
Can i have your email? to send you my info.list
Andrea - 05 October 2015 at 20:27
Sure, send it to [email protected]
- 04 October 2015 at 00:06
Hey andrea i download the swift 2 version of this proyect and i'm having a problem to run the app with my server url, i tried to write this xml NSAppTransportSecurity NSAllowsArbitraryLoads to info list but i still having this error App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file. error
Andrea - 04 October 2015 at 08:42
Hi Alex, be sure to write it exactly like that: https://www.kaleidosblog.com/tutorial/NSAppTransportSecurity.txt If you still can't access to the http links, be free to send me your info.plist to let me check it out.
- 02 October 2015 at 06:46
Hi great tutorial, it is really helpful tutorials like this one, but i'm having a problem whit the HttpMethod on swift 2, can anyone help me? thank you:)
Andrea - 02 October 2015 at 09:12
Hi, thank you for your comment. Here you can fine the updated example for swift 2. https://www.kaleidosblog.com/tutorial/json_table_view_images_swift_2.zip . With the new iOS update the http links are not allowed anymore, you should use the https protocol. However, you can still allow this kind of traffic from specific urls by editing the info.plist file. Moreover the httpmethod is deprecated and replaced with the nsurlsession method. In the updated example you'll see everything :) Thank you again





AboutContactPrivacy