Diffable Data Sources & Compositional Layouts Part 2

Yoel Lev
5 min readAug 31, 2019

--

Welcome back, I hope you enjoyed part one of Diffable Data Sources & Compositional Layouts as today we are going to populate our collection view with data from a remote server.

We’re going to go over a few subjects

  1. Build a Network Service using a Singleton pattern.
  2. Create and use a Decodable object.
  3. Download data and parse it using JSONDecoder.
  4. Update a CollectionView using the newly iOS 13 DiffableDataSource.
  5. Finally as a bonus, use NSCache to save and reload downloaded images.

Our final project will look like this:

Getting Started

Let's open our project from part one of Diffable Data Sources & Compositional Layouts, if you did not follow it, you can find the link down bellow.

Open CollectionViewController.swift and add a snapshot variable just above our iOS life cycle methods. Comment out the createData() function as we won’t be using it.

//1. Snapshot
private var snapshot = DataSourceSnapshot()
//.2 Comment out
//createData()

Add both function declartions below viewDidLoad()

func setupUIRefreshControl(with collectionView: UICollectionView) { }@objc func handleRefresh() { }

In the setupUIRefreshControl function we will add the call which will trigger our network fetch.

//.1
let refreshControl = UIRefreshControl()
//.2
refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
//.3
refreshControl.tintColor = .gray
//.4
collectionView.refreshControl = refreshControl
  1. We start by creating a UIRefreshControl, a UIRefreshControl object is a standard control that you attach to any UIScrollViewObject. When the user drags the top of the scrollable content area downward, the scroll view reveals the refresh control and begins animating its progress indicator.
  2. Add a target to the refreshControl, this will call the handleRefresh function upon draginng the collection view downward.
  3. Give the spinner a color.
  4. Add the refresh controller to our associated collection View UIRefreshControl.
  5. Add func setupUIRefreshControl(with: collectionView) to the viewDidLoad function under func configureDataSource()

In ConfigureDataSource() comment out the following code:

Build and run

You should have an empty collection view. Drag down to initiate the UIRefresh controll.

Network Service

Start by creating a new swift file and name it JSONContact.

Add the following code:

struct JSONContact: Decodable,Hashable {
let name: String
let imageUrl: String
}

Here you have created a Contact which will be decoded from the fetched JSON representation.

Next, create a new file and name it NetworkService, this class will be a Singleton pattern. A singleton is a class that allows only a single instance of itself to be created and gives access to that created instance.

Add the following code:

import UIKitclass NetworkService {    //.1   
static
let shared = NetworkService()
//.2
private let
urlString = "https://demo-profiles.s3.eu-west-2.amazonaws.com/profileDemo.json"
//.3
func downloadContactsFromServer(completion: @escaping (Bool,[Contact]) -> Void ) {
var contacts = [Contact]() print("---------------------------------")
print("Attempting to download contacts..")
print("---------------------------------")
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, resp, err) in
if let err = err {
print("---------------------------------")
print("Failed to download contacts:",err)
print("---------------------------------")
completion(false,[])
return
}
guard let data = data else { return }
let jsonDecoder = JSONDecoder()

do {
let jsonContacts = try jsonDecoder.decode([JSONContact].self, from: data)
jsonContacts.forEach({ (jsonContact) in

contacts.append(Contact(name: jsonContact.name, image: jsonContact.imageUrl))
})} catch let jsonDecodeErr { print("-------------------------------------------")
print("Failed to decode with error:", jsonDecodeErr)
print("-------------------------------------------")
completion(false,[])
}
print("-----------------------")
print("Finished downloading...")
print("-----------------------")
completion(true,contacts)
}.resume()
}
}
  1. We create a static constant which will be shared between objects of the class.
  2. The urlString is a link to a JSON file I prepared earlier and is located in a AWS S3 bucket. You can copy and paste it into the browser and have a look at the data to understand how it is structured, basically an array of contacts.
  3. Finally, downloadContactsFromServer which will fetch our data from the remote server. Here we use JSONDecoder to parse the returned data into our JSONContact struct. With this we will be able to create and append each contact to the contacts array. We then return our populated contacts array with the function completion block.

Handle Refresh

Back to CollectionViewController, add the following code bellow setupUIRefreshControl function.

   NetworkService.shared.downloadContactsFromServer { (done,   fetchedContacts) in  if !done {
print("-----------------------------------------")
print("- Error while fetching data from server -")
print("-----------------------------------------")
}else {
DispatchQueue.main.async {
self.snapshot.deleteAllItems()
self.snapshot.appendSections([Section.main])
self.snapshot.appendItems(fetchedContacts)
self.dataSource.apply(self.snapshot, animatingDifferences: true)
}
}
DispatchQueue.main.async{
self.collectionView.refreshControl?.endRefreshing()
}
}
}

Upon completion of a successful call, fetchedContacts will contain our parsed data, we then dispatch back to the main queue and update our collection view by deleting previous items, appending our .main section, appending our fetched contacts and apply the changes on the data source.

We then call the endRefreshing function to stop and hide the spinner animation.

Currently our contacts array contains the Contacts name and the string URL from which we should download its profile image. all that is left to do is to download each image from the given URL an update the image. For this step we will use NSCache.

Using NSCache

We will use a UIImageView Extension method which I have written, to download and cache the images when necessary, it is named:

func loadImageUsingCacheWithUrlString(urlString:String) and loacted in the UIImageViewEx.swift file.

NSCache is a mutable collection where you can temporarily store momentary key-value pairs that can be evicted when resources are low.

You typically use NSCache objects to temporarily store objects with transient data that are expensive to create. Reusing these objects can provide performance benefits, because their values do not have to be recalculated. However, the objects are not critical to the application and can be discarded if memory is tight.

for more info about NSCache:

Updating the Contact Cell with the fetched data

Open ContactCell.swift and replace with the following code

var contact: Contact? {
didSet {
nameLbl.text = contact?.name
//Use NSCache to download the profile image
contactImage.loadImageUsingCacheWithUrlString(urlString:
contact?.image ?? "")
}
}

Build run and pull to refresh…

You should have successfully loaded your collection view with the data from the server.

👏🏼 Great Job! 👏🏼

In conclusion

You have learned how to fetch data from a server and populate it into a collection view. We used JSONDecoder to decode our data, and parse it as Contacts, Finally we used NSCache to save and reuse our downloaded images.

I hope you enjoyed this tutorial, if you have any questions or ways for me to improve it please let me know.

Cheers

--

--

Yoel Lev

Senior iOS Developer @ TinyTap, Creator of Wavez. #wavez_app