During the last week I was heavily working with UICollectionViewDiffableDataSource
s at work. We are currently creating an app which contains a lot of collection views which are constantly updating and changing. So diffable datasources were a perfect fit. From the UI department we got the task to implement empty state views for all of our lists. So I sat down and was searching for a way to directly bake in the empty state view inside the the diffable datasource without having the need to handle the state anywhere else. In the following I want to provide you with my findings and the code I came up with, to enhance a UICollectionViewDiffableDataSource
with an empty state view.
Code
The following code block demonstrates my solution to have an empty state view directly tied to an UICollectionViewDiffableDataSource
.
/// A diffable data source fir UICollectionViews that is capable of displaying an UIView if the datasource does not contain any item
class EmptyableDiffableDataSource<S, T>: UICollectionViewDiffableDataSource<S, T> where S: Hashable, T: Hashable {
private var collectionView: UICollectionView
private var emptyStateView: UIView?
/// Creates a diffable data source with the specified cell provider, and connects it to the specified collection view. Additionally an `UIView` can be specified which is shown when the data source is empty
///
/// - Parameters:
/// - collectionView: The initialized collection view object to connect to the diffable data source.
/// - cellProvider: A closure that creates and returns each of the cells for the collection view from the data the diffable data source provides.
/// - emptyStateView: An UIView that is displayed when the data source is empty. If nil is provided, the default view for the collection view is shown
convenience init(collectionView: UICollectionView,
cellProvider: @escaping UICollectionViewDiffableDataSource<S, T>.CellProvider,
emptyStateView: UIView? = nil
) {
self.init(collectionView: collectionView, cellProvider: cellProvider)
self.collectionView = collectionView
self.emptyStateView = emptyStateView
}
override func apply(_ snapshot: NSDiffableDataSourceSnapshot<S, T>, animatingDifferences: Bool = true, completion: (() -> Void)? = nil) {
super.apply(snapshot, animatingDifferences:animatingDifferences, completion: completion)
guard let emptyView = emptyStateView else {
return
}
if snapshot.itemIdentifiers.isEmpty {
// Add and show empty state view
emptyView.translatesAutoresizingMaskIntoConstraints = false
collectionView.addSubview(emptyView)
NSLayoutConstraint.activate([
emptyView.topAnchor.constraint(equalTo: collectionView.layoutMarginsGuide.topAnchor),
emptyView.trailingAnchor.constraint(equalTo: collectionView.layoutMarginsGuide.trailingAnchor),
emptyView.bottomAnchor.constraint(equalTo: collectionView.layoutMarginsGuide.bottomAnchor),
emptyView.leadingAnchor.constraint(equalTo: collectionView.layoutMarginsGuide.leadingAnchor)
])
} else {
NSLayoutConstraint.deactivate([
emptyView.topAnchor.constraint(equalTo: collectionView.layoutMarginsGuide.topAnchor),
emptyView.trailingAnchor.constraint(equalTo: collectionView.layoutMarginsGuide.trailingAnchor),
emptyView.bottomAnchor.constraint(equalTo: collectionView.layoutMarginsGuide.bottomAnchor),
emptyView.leadingAnchor.constraint(equalTo: collectionView.layoutMarginsGuide.leadingAnchor)
])
// Remove and hide empty state view
emptyStateView?.removeFromSuperview()
}
}
}
Explanation
- Line 2: Extend the base
UICollectionViewDiffableDataSource
class - Line 3-4: Keep references to the
UICollectionView
on which the data source is applied and a potential empty state view - Line 13-21: Initialize the base class with the
super.init
call and set the view references - Line 23-24: Override the base class
apply
method with a custom one which handles the empty state view - Line 26-28: If no empty view was supplied, do not calculate anything and just return the method
- Line 30: If the
itemIdentifiers
of the snapshot are empty, show the empty state view by activating the auto layout constraints and adding it as asubview
to thecollectionView
, referenced in theinit
- Line 41: If the item
itemIdentifiers
of the snapshot are not empty, deactivate corresponding auto layout constraints and remove the empty state view from the superview.
Usage
The usage is rather simple. The only difference from calling the standard initializer is, that you can provide an optional UIView
to be displayed when the datasource is empty.
let dataSource = EmptyableDiffableDataSource<CollectionViewListSection, CollectionViewListCell>(
collectionView: aCollectionView,
cellProvider: aCellProviderClosure,
emptyStateView: anEmptyView
)
Conclusion
With this small custom UICollectionViewDiffableDataSource
class we are now able to define any UIView
instance to be shown, when the datasource does not contain any items.
I hope you found this small tip useful. If you have suggestions or question don't hesitate to reach out to me !
See you next time 👋