Mixing DiffableDataSource with UIKit and Combine Part.1 : iOS Movies Catalogue

Ghazi Tozri
DataSeries
Published in
8 min readJan 14, 2021

At WWDC 2020, Apple introduced some cool new features and improvements for both Table views and Collection views. One of those is called UITableViewDiffableDataSource .

Diffable data sources dramatically simplify the work involved in managing and updating collection and table views to create dynamic and responsive experiences in your apps — Apple, WWDC 2020.

In this article, i will try my best to make a simple, but realistic project presenting this new source of truth, as Apple likes to call it.
Developing an iOS project in 2021, means that it has to contain some of the latest technologies to be up to date, so we will use basic Combine Framework Publishers and MVVM pattern.
Sadly, SwiftUI won’t be included in this article, but don’t be disappointed ! Because the second part of this article will be all about migrating from UIKit to SwitUI.

Example

Requirements:
1. Swift 5, iOS 13, Xcode 11
2. Register to IMDB API to get a valid key, or just use the key included in the final result of the project.
3. Helpers files, that can be copied or downloaded from Github. These files will help us keep this project simple.

Objectifs:
1. Use MVVM pattern and Combine in a realistic project.
2. Learn how to implement UITableViewDiffableDataSource and NSDiffableDataSourceSnapshot.
3. Use @Published variables to monitor keystrokes in UISearchBar.

For those who want to download the final result of this project and follow the steps below, you can pull it from my Github repo.

First things first, create a new iOS 13 project, select the old fashion UIKit storyboard and let’s get started.

Setting the MVVM pattern begins with refactoring the project folders and files, this step is arbitrary because it depends on the developer’s preference but we’re going to do it anyway.
We need three basic swift files : Model.swift, View.swift (i called it MainView.swift) which is the default ViewController created by xCode on project creation, and ViewModel.swift.
Plus, i added the Service.swift to the group, this class will contain the networking logic.
If you are interested in diving deeper into the MVVM pattern, check MVVM: A Tutorial and Practical Example in Swift, by Steven Curtis.

Refactor project to MVVM pattern
MVVM refactored project files

Next step is creating our view, we will use the Main.storyboard. We need a UILabel for title, UISearchBar for text input by user and a UITableView to display result for each search action.
Use ⌘+⇧+L to quickly open components Library then drag and drop the previously mentioned components above and add needed constraints and setting the class name.
* PLEASE DON’T FORGET to attach UITABLEVIEW DELEGATE to the main view. *

MainView Scene

Now, open your MainView.swift file and add the @IBOutlets, import Combine for later use. It should look something like this :

init MainView.swift

In order to have a fully functional UITableView that displays awesome movies, we need to create the Movie Cell and register it to the UITableView.
Use ⌘+N to create a new file and choose cocoa Touch class. Give your cell a cool name and DON’T forget to check also create XIB file.

Creating MovieCell files

Open the MovieCell.xib file and design your cool cell the way you like, just make sure it contains a UIImageView for movie poster, first UILabel for movie title and a second UILabel for movie description.

MovieCell.xib file

Let’s take a pause from xCode and check what type of response we will have from IMDB API if we look for “pulp fiction”, the URL must respect this format : “https://imdb-api.com/en/API/SearchMovie/” + API_KEY + “/” + INPUT
Our URL will be : https://imdb-api.com/en/API/SearchMovie/k_0hpcd8sn/pulp fiction
Put that URL in your favorite navigator, you will have this response :

IMDB API response

Keep that page aside and open Quicktype website, go back to the response page and choose “raw data” tab in top left corner then copy-paste the JSON response in Quick-Type left section. Wait for a moment and you will have the models for our cool Movies almost ready !

Result Model

Copy-paste the models to your Model.swift file, we have some minor changes to make. First, we will use the Decodable protocol instead of Codable protocol. If you want to know the differences between these two protocols, refer to Conforming to Codable vs Encodable and Decodable, by Paul Hudson.

We will add another important protocol but we need to talk about it first and pay attention to it.
Every UITableViewDiffableDataSource has two generic models : SectionIdentifierType that represents the type of UITableView section and ItemIdentifierType that represents the type of items, in other words, the type of information that will be displayed by the tableView. Both of these types MUST conform to Hashable protocol.

UITableViewDiffableDataSource documentation

The Result Model will be used as our Items Type, so it needs to contain a function that hashes items based on their unique identifiers :

hash function

If we don’t add that function, the compiler will have confusions identifying the unique items, the application will most likely crush.

Finally, our Models are done.

Model.swift file

Now we will define our custom class MoviesTableViewDiffableDataSource of type UITableViewDiffableDataSource<String?, Result> .

If you were paying attention to the Hashable protocol part, you will figure out that the first Class of type String? means that our tableView sections will be of type String? and the second Class represents the Item type which is Result and both of them conforms to Hashable protocol.
Go to your MainView and add the MoviesTableViewDiffableDataSource declaration extending UITableViewDiffableDataSource and register our cool movieCell.

Defining MoviesTableViewDiffableDataSource

Go back to the MovieCell.swift file and add a new object of type Result and a function to setup the data from that object to the UI whenever the movie object changes.
PS: I used SDWebImage pod for image cache support, also to get that sexy loader when the image is being displayed from the movie object.

MovieCell.swift file

Run the project to check if there’s any errors.

First part result

The first part of this article is done, the rest contains all the fun ! Go to your MainView and make sure you are importing Combine.

Then add a new variable of type @Published var keyStroke: String = “”
This @Publisher is used when we want that object to publish its value whenever it changes, using the UISearchBar Delegate, we will use it as a monitor for user keystrokes in Search bar textfield.

Sinking that @Published object will return value of the property whenever it changes. Use it with properties of classes, not with non-class types like structures.
Initial state: When the class is being created, the keystroke value should be be empty ( “” )
Text did change: when the user writes something, it will be observed by the UISearchBar callback textDidChange. So we need to assign that new text to our @Published variable.
Text is cleared: When the user clears the text in search textfield, it will be observed by searchBarCancelButtonClicked callback. In that case we need to set the @Published variable to empty text.

Implementing @Published to UISearchBar Delegate

As I mentionned earlier that @Published propriety can be observed.
This operation is called “Sink” which uses the $ symbol and receive it ON the main queue. For this purpose we used to write DispatchQueue.main.asyncbut Apple introduced RunLoop.main instead to be used with Combine tasks.
The result must be stored in a Cancellable Set.

We will wrap the whole code in a single function setupObservers() and call it in viewDidLoad function.
Run the application, start typing and pay attention to what the console is printing.

setting up observers
loggin console

We have completed the implementation of our view and observers, now we need to configure the ViewModel class.
Let me introduce you to the NSDiffableDataSourceSnapshot.

The data in a snapshot is made up of the sections and items you want to display, in the specific order you want to display them.

The ViewModel class will contain : a cancellable to store results, @Published variable to monitor changes made in MainView text, a snapshot and a diffableDataSource instance.

We need to add the fetchMovies function, obviously it will call the network service provider that we will implement it in Service.swift file.
The viewModel constructor will initialize the keyWordSearch published, this processs will be repeated whenever its value did not recieve any changes during 0.5 second, calling the fetchMovies function.

ViewModel init

We will use the Singleton pattern, in order to fetch movies from IMDB API.
That means we need to make a private constructor and static object of our class.
Nothing fancy, just an ordinary URLSesssion dataTask. It should accept one parameter (Keystroke), dont forget to include your API key in the endpoind.

Networking call

Go back to ViewModel.swift file, add an instance of the service provider using its sharedInstance :

let serviceProvider = Service.sharedInstance

Now call fetchFilms inside the fetchMovies function for keyWordSearch, it will return an object of type arry of Result:

serviceProvider.fetchFilms(for: keyWordSearch) { (<#[Result]#>) in
<#code#>
}

To display data provided by the IMDB API in a view using a snapshot:

  1. Create a snapshot and populate it with the state of the data you want to display, sections and items.. Or removing them !
    snapshot.appendSections([Section])
    snapshot.appendItems(item, toSection: Section)
    snapshot.deleteAllItems()
  2. Apply the snapshot to reflect the changes in the UI : diffableDataSource.apply(snapshot)

PS_1: We need to check if the diffableDataSource is not nil to avoid any unwanted behaviours and crushes.
PS_2: We need to handle the condition of empty array of results.
PS_3: Our Section will be an empty String (“”)

Go back to the MainView and add an instance of type ViewModel.
In the setupObservers function, bind both publishers together :

// MONITOR search bar textfield keystrokes
$keyStroke
.receive(on: RunLoop.main)
.sink { (keyWordValue) in
print(keyWordValue)
self.viewModel.keyWordSearch = keyWordValue
}.store(in: &cancellables)

Finally we will implement the UIDiffableDataSource ! But first let’s take a close look at the documentation again :

The CellProvider contains 3 attributes : a tableView, an indexPath and an item Model. It must returns a UITableViewCell.

UIDiffableDataSource

That CellProvider will give us the ability to get rid of cellForRowAt and numberOfRows functions. Plus, it has a simple implementation ! We just need to dequeue the cell and set the cell’s movieObject to item Model provided by CellProvider. Dont forget to return the cell !

Implementing DIFFABLE DS

The whole code for MainView :

Run your application and enjoy that cool free animation !

--

--

Ghazi Tozri
DataSeries

I write about Apple news, Swift, and SwiftUI in particular, and technology in general.