UITableViewDiffableDataSource: example in Objective-C

Hi,
I am trying to figure out how to use UITableViewDiffableDataSource in Objective-C, and I can't find any documentation or example on how to do this. How do I create a new UITableViewDiffableDataSource? In particular, the syntax is really confusing, and I also can't figure out how to create Decodable objects that can be used as items.

If someone has a basic example they can post, or point to an article or something that might have discussed this, it would be really helpful. I've Googled far and wide but haven't found anything.
Can you be a bit more specific about the problems you are encountering? The API in ObjC is not very different than in Swift. You create a diffable data source using initWithTableView:cellProvider:. The cell provider basically replaces the old tableView:cellForRowAtIndexPath: call.

Then you create snapshots and apply them to your data source.

The items need to implement equality (isEqual:, hash).

I would recommend to check out the WWDC videos from last year (Advances in Collection View Layouts) and from this year (Advances in Diffable Data Sources). There is also a sample project that shows how it is used, which got updated this year with all the latest APIs.
Thanks for the reply. I'm confused about how to setup the UIDiffableDataSource object. I can setup the cellProvide first:

Code Block
UITableViewDiffableDataSourceCellProvider cellProvider = ^UITableViewCell*(UITableView * tableView, NSIndexPath *indexPath, NSDictionary *itemDictionary) {
            CJSiriShortcutsSettingsTableViewCell *cell = (CJSiriShortcutsSettingsTableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"ShortcutsCellIdentifier" forIndexPath:indexPath];
/// ... setup the cell here
}


Then I want to setup the datasource object, and I'm wondering if this is the right way to do it:

Code Block
        UITableViewDiffableDataSource <SiriShortcutsSection, NSDictionary *> *dataSource = [[UITableViewDiffableDataSource alloc] initWithTableView:self.tableView cellProvider:cellProvider];
        self.tableView.dataSource = dataSource;


The 'SiriShortcutsSection' is a enum:

Code Block
typedef enum : NSUInteger {
    SiriShortcutsSectionExisting,
    SiriShortcutsSectionAdd
} SiriShortcutsSection;


The compiler is giving an error:

Type argument 'SiriShortcutsSection' is neither an Objective-C object nor a block type


How do I define that 'section' object? And can I pass in an 'NSDictionary' for the item, or do I have to implement isEqual and hash on that as well? Should it be a custom object with an NSDictionary inside it?

Thanks for the help.




So I created a new class for that 'section' object and it works (compiles, at least):

Code Block
@interface CJSiriShortcutsSectionIdentifier : NSObject
@property (nonatomic) SiriShortcutsSection sectionType;
@end


When I use it in a snapshot, and 'apply', it produces an empty tableview:

Code Block
    NSDiffableDataSourceSnapshot<CJSiriShortcutsSectionIdentifier *, NSDictionary *> *snapshot = [[NSDiffableDataSourceSnapshot alloc] init];
    if (self.existingShortcutsSortedArray.count) {
        CJSiriShortcutsSectionIdentifier *sectionExisting = [[CJSiriShortcutsSectionIdentifier alloc] initWithSection: SiriShortcutsSectionExisting];
        [snapshot appendSectionsWithIdentifiers: @[sectionExisting]];
        [snapshot appendItemsWithIdentifiers:self.existingShortcutsSortedArray intoSectionWithIdentifier:sectionExisting];
    }
    UITableViewDiffableDataSource <CJSiriShortcutsSectionIdentifier *, NSDictionary *> *dataSource = self.tableView.dataSource;
    [dataSource applySnapshot:snapshot animatingDifferences:YES];


I know that self.existingShortcutsSortedArray has an array of NSDictionary objects.

Is there something I'm missing? Do I need to implement isEqual on the CJSiriShortcutsSectionIdentifier class? Or something else as well?


So the problem was something else. In viewDidLoad, I setup the datasource and assign it to the tableView, and create an empty initial snapshot, since the data hasn't loaded yet :

Code Block
    UITableViewDiffableDataSource <CJSiriShortcutsSectionIdentifier *, NSDictionary *> *dataSource = [[UITableViewDiffableDataSource alloc] initWithTableView:self.tableView cellProvider:cellProvider];
    self.tableView.dataSource = dataSource;
    NSDiffableDataSourceSnapshot<CJSiriShortcutsSectionIdentifier *, NSDictionary *> *snapshot = [[NSDiffableDataSourceSnapshot alloc] init];
    [dataSource applySnapshot:snapshot animatingDifferences:NO];


Then in viewWillAppear, I make an async call to get the latest data, and then try to apply the snapshot there, using the tableview's datasource object, but that seemed to be 'nil' at that point for some reason:

Code Block
/*... after viewWillAppear loads data with an async call, call this on the dispatch_get_main_queue */
    NSDiffableDataSourceSnapshot<CJSiriShortcutsSectionIdentifier *, NSDictionary *> *snapshot = [[NSDiffableDataSourceSnapshot alloc] init];
    if (self.existingShortcutsSortedArray.count) {
        CJSiriShortcutsSectionIdentifier *sectionExisting = [[CJSiriShortcutsSectionIdentifier alloc] initWithSection: SiriShortcutsSectionExisting];
        [snapshot appendSectionsWithIdentifiers: @[sectionExisting]];
        [snapshot appendItemsWithIdentifiers:self.existingShortcutsSortedArray intoSectionWithIdentifier:sectionExisting];
    }
    UITableViewDiffableDataSource <CJSiriShortcutsSectionIdentifier *, NSDictionary *> *dataSource = self.tableView.dataSource; /* THIS IS NIL*/
    [dataSource applySnapshot:snapshot animatingDifferences:YES];


Turns out, the problem was that the UITableViewDiffableDataSource needed to be strongly retained by the view controller. Now it works fine, and loads the data into the tableview!

Thanks for the help.

The only question left is this: do we need to implement the hash and isEqual: methods as well in the Objective-c class? Or does that already happen with Objective-C?
Hey,

good to hear you figured it out.

As you have seen, in ObjC we can't support c enums as we require you to provide a pointer. You can box your enum the way you did with the section object, in that case though you should implement isEqual: and hash. When just boxing an enum, the easiest way to do that would probably be to just compare the other object's enum value in isEqual: and to return the enum's raw value as the hash.

Another good way to do this is to use some sort of identifier as your section and/or item identifiers. Using a dictionary as the item identifier is certainly possible although a bit unconventional. Usually you would have your existing data model and data store and then use the item's identifier (for example an NSUUID or an NSString that you get from a server as the item's id) as the item identifier. Inside of your cell configuration you can then read this identifier, fetch the associated object from your actually data store and configure the cell.

In general we do not suggest to use the data source as your source of truth, but rather as your view model. Although we do have samples that illustrate how to store your actual data as the item identifier, for more complex data types it is usual advisable to use the objects identifier.

A simple example: say you fetch a couple of social media posts from a server. You likely have a data model backed by a class named Post or something similar. This class would then probably have properties like author, message, date and identifier. The identifier property is usually a string or a uuid, but could also be a number or any other type. Now you can either use this Post object as item identifier, in which case isEqual: and hash should be implemented and be based on the identifier property, or you might already have some sort of local cache or database that holds all the posts known to the app. In the latter case you probably already have a method like postForIdentifier: which reads the post object from a local mapping. At this point it is usually simpler / easier to just use a post's identifier directly as the item identifier and then just fetch the post object inside your cell's configuration. If the identifier is one of Foundation's basic objects like NSString, NSUUID, NSNumber,... it already has an implementation for isEqual: and hash, so you don't have to provide one yourself.
Thanks for the reply; that's really helpful. I have it working now. To resolve my confusion, I created separate classes for both the SectionIdentifier and ItemIdentifier, and for both of them, implemented isEqual: and hash. For the ItemIdentifier, in the isEqual: and hash implementations, I had to include *both* the uniqueID and any other property that was possible to update. That way, when those properties change, they would reload that UITableViewCell automatically as well.



UITableViewDiffableDataSource: example in Objective-C
 
 
Q