Using DiffableDataSource and Combine.

Tung Vu Duc 🇻🇳
6 min readJan 25, 2021
Photo by cottonbro from Pexels

Visit my website to read the original post:

http://learnwithtung.co/2021/01/using-diffabledatasource-combine/

DiffableDataSource is an API was introduced in iOS 13. It helps us more efficiently and easily setup datasource for a table view or collection view. Today, we’re gonna increase its powerful by using it with the Combine framework. OK, let’s see how it work together.

Before start feel free to download and then open the starter project:

Networking with Combine

After you open the project. Let’s move to APIService.swift file and add this code bellow the line //Write your code below :

// 1
func fetchUsers() -> AnyPublisher<[User], Error>{
// 2
let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
// 3
let publisher = URLSession.shared.dataTaskPublisher(for: url)
// 4
.map(\.data)
// 5
.decode(type: [User].self, decoder: JSONDecoder())
// 6
.eraseToAnyPublisher()
// 7
return publisher
}
  1. We define the function in order to gets data from API and then returns a publisher will emit either a list of User or an error if something went wrong.
  2. Very native we create a URL from the string.
  3. If before we have something like URLSession.shared.dataTask(with: URL) and then call resume() now by using combine we create a publisher responsible for download data from URL by calling dataTaskPublisher(for: URL) .
  4. Using map key path operator in order to get only data from response
  5. We use another operator called decode. The operator will try to decode the data above and map it to given type by given decoder.
  6. A Special operator helps us hide all the return detail types
  7. Finally, we return the publisher, clients will free to call and interact with it.

Now open FeedViewController.swift, add this line of code right bellow where the class was defined and above @IBOutlet weak var tableView: UITableView!:

// 1
private var subscriptions = [AnyCancellable]()
// 2
@Published var users = [User]()
  1. To store publishers in this list and Combine will automatically release all publisher for you when the view controller deallocate.
  2. To store list users we fetched from URL. Notice that it’s not just an array, we make it a publisher by mark @Published.

Then add this code bellow viewDidLoad() to use what we created above:

private func loadPosts(){
// 1
APIService.shared.fetchUsers()
// 2
.sink { (error) in
// handle error
// 3
} receiveValue: { (users) in
self.users = users
}
// 4
.store(in: &subscriptions)
}
  1. Call fetchUsers method from APIService.
  2. Listen to error. In the scope of this post, we do nothing with any errors.
  3. Listen to list of users. When users received we store if in our property.
  4. Store this publisher to the subscriptions.
loadPosts()

Using DiffableDataSource and Combine.

DiffableDataSource is an API was introduced in iOS 13. It helps us more efficiently and easily setup datasource for a table view or collection view. Today, we’re gonna increase its powerful by using it with the Combine framework. OK, let’s see how it work together.

Before start feel free to download and then open the starter project:

https://github.com/LearnWithTung/DiffableCombine/tree/master/starter

Networking with Combine

After you open the project. Let’s move to APIService.swift file and add this code bellow the line //Write your code below :

// 1
func fetchUsers() -> AnyPublisher<[User], Error>{
// 2
let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
// 3
let publisher = URLSession.shared.dataTaskPublisher(for: url)
// 4
.map(\.data)
// 5
.decode(type: [User].self, decoder: JSONDecoder())
// 6
.eraseToAnyPublisher()
// 7
return publisher
}
  1. We define the function in order to gets data from API and then returns a publisher will emit either a list of User or an error if something went wrong.
  2. Very native we create a URL from the string.
  3. If before we have something like URLSession.shared.dataTask(with: URL) and then call resume() now by using combine we create a publisher responsible for download data from URL by calling dataTaskPublisher(for: URL) .
  4. Using map key path operator in order to get only data from response
  5. We use another operator called decode. The operator will try to decode the data above and map it to given type by given decoder.
  6. A Special operator helps us hide all the return detail types
  7. Finally, we return the publisher, clients will free to call and interact with it.

Now open FeedViewController.swift, add this line of code right bellow where the class was defined and above @IBOutlet weak var tableView: UITableView!:

// 1
private var subscriptions = [AnyCancellable]()
// 2
@Published var users = [User]()
  1. To store publishers in this list and Combine will automatically release all publisher for you when the view controller deallocate.
  2. To store list users we fetched from URL. Notice that it’s not just an array, we make it a publisher by mark @Published.

Then add this code bellow viewDidLoad() to use what we created above:

private func loadPosts(){
// 1
APIService.shared.fetchUsers()
// 2
.sink { (error) in
// handle error
// 3
} receiveValue: { (users) in
self.users = users
}
// 4
.store(in: &subscriptions)
}
  1. Call fetchUsers method from APIService.
  2. Listen to error. In the scope of this post, we do nothing with any errors.
  3. Listen to list of users. When users received we store if in our property.
  4. Store this publisher to the subscriptions.

Inside viewDidLoad() after super.viewDidLoad() we call the function:

loadPosts()

Setup datasource using DiffableDataSource

Below @IBOutlet weak var tableView: UITableView! we define data source type and snapshot type like this:

// 1
typealias DiffableDataSource = UITableViewDiffableDataSource<Int, User>
// 2
typealias Snapshot = NSDiffableDataSourceSnapshot<Int, User>
  1. Define our data source type as UITableViewDiffableDataSource of section as Int, and cell model as User.
  2. Define snap shot type as NSDiffableDataSourceSnapshot of type section as Int, and cell model as User.

It’s important that data source type and snap shot type must match.

Then inside viewDidLoad() bellow where we call loadPost() Add this code:

// 1
let dataSource = DiffableDataSource(tableView: tableView) { (tableView, indexPath, user) -> UITableViewCell? in
// 2
let cell = tableView.dequeueReusableCell(withIdentifier: "FeedCell", for: indexPath) as! FeedCell
// 3
cell.configure(with: user)
// 4
return cell
}
  1. Initial datasource by passing a table view and a closure.
  2. Create cell by a native method dequeueReusableCell
  3. Configure cell with a user
  4. Return the cell.

Straightforward so far, isn’t it ? We almost have everything we need, let’s try to run our application for the first time.
We see… nothing but an empty table view. What’s missing here ? If you notice we haven’t had something like reloadData() as we usually do in native way, right ?

But by using Combine we don’t use that way to refresh tableview, as I mention before posts is also a publisher and it’s responsible for listening changes itself, we will use that property to manipulate our tableview. Add this code inside viewDidLoad() and bellow where dataSource declared :

// 1
$users
// 2
.receive(on: DispatchQueue.main)
// 3
.sink {[weak self] (_) in
// 4
self?.applySnapshot(to: dataSource)
}
// 5
.store(in: &subscriptions)
  1. Notice that we need to prefix $ sign in order to use users as a publisher not its value.
  2. Use this operator in order to dispatch changes to main queue.
  3. By calling sink we receive values when users changes. In this case, we update table view regardless changes.
  4. This method will be define bellow.
  5. Store this publisher to the subscriptions

And then add this function bellow viewDidLoad() :

private func applySnapshot(to dataSource: DiffableDataSource) {
// 1
var snapshot = Snapshot()
// 2
snapshot.appendSections([0])
// 3
snapshot.appendItems(users)
// 4
dataSource.apply(snapshot)
}
  1. Create a snapshot
  2. Add sections to the snapshot. In this case we only have one section so we can simply append [0]
  3. Append current state of users to the snapshot.
  4. Apply the snapshot to the datasource

DiffableDataSource use snapshot mechanism every time you apply a snapshot to the datasource it will compare the new to the old and change the tableview if needed.

Good job ! I think we got everything we need. Let’s run our application. Yes !! We finally see what we want 🥰. Try to run again but this time focus on how DiffableDataSource handle the fantastic animation for us. 🎇

You can download the final project here:

https://github.com/LearnWithTung/DiffableCombine/tree/master/final

Conclusion

In this post I showed you how to use DiffableDataSource and Combine together. I hope you got the concept, don’t forget to practice more I believe you’ll familiar it easily.

In part 2, I’ll show you how to custom a DiffableDataSource and interact with tableview such as swipe to delete.

If you have any questions please feel free to comment in the comment section or mail me. Don’t forget to share, claps for this post and visit my website to support me, thank you.

--

--

Tung Vu Duc 🇻🇳

Passionate about writing good software. Contact me: 📮tungvuduc2805@gmail.com