Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Source code to use with Xcode playground related to the blog post on emptytheory.com at https://emptytheory.com/2021/01/11/using-ios-diffable-data-sources-with-different-object-types/
import UIKit
import PlaygroundSupport
/// Simple sample diffable table view to demonstrate using diffable data sources. Approximately 33% of the time, it should show "bad weather" UI instead of apples and oranges
final class DiffableTableViewController : UIViewController {
var tableView: UITableView!
enum Section: String, CaseIterable, Hashable {
case apples = "Apples"
case oranges = "Oranges"
case empty = "Bad Weather Today!"
}
private lazy var dataSource: DiffableViewDataSource = makeDataSource()
override func viewDidLoad() {
super.viewDidLoad()
//Set up the table view
tableView = UITableView(frame: view.frame, style: .plain)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "AppleCell")
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "OrangeCell")
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "EmptyDataCell")
tableView.dataSource = dataSource
self.view.addSubview(tableView)
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
view.setNeedsUpdateConstraints()
// Just a silly method to pretend we're getting empty data every 3rd or so call (for demo purposes every 3 days or so we get rain at the fruit stand)
Int.random(in: 0..<3) > 0 ? getData() : getEmptyData()
}
/// Update the table with some "real" data (1 apple and 1 orange for now)
private func getData() {
DispatchQueue.global().async {
//Pretend we're getting some data asynchronously
let apples = [Apple(name: "Granny Smith", coreThickness: 12)]
let oranges = [Orange(name: "Navel", peelThickness: 3)]
DispatchQueue.main.async {
//Have data
self.updateTable(apples: apples, oranges: oranges)
}
}
}
/// Update the table with empty data
private func getEmptyData() {
DispatchQueue.global().async {
//Pretend we're getting some data asynchronously and it fails
DispatchQueue.main.async {
//Have data
self.updateTable(apples: [], oranges: [])
}
}
}
/// Update the data source snapshot
/// - Parameters:
/// - apples: Apples if any
/// - oranges: Oranges if any
private func updateTable(apples: [Apple], oranges: [Orange]) {
// Create a new snapshot on each load. Normally you might pull
// the existing snapshot and update it.
var snapshot = NSDiffableDataSourceSnapshot<Section, AnyHashable>()
defer {
dataSource.apply(snapshot)
}
// If we have no data, just show the empty view
guard !apples.isEmpty || !oranges.isEmpty else {
snapshot.appendSections([.empty])
snapshot.appendItems([EmptyData()], toSection: .empty)
return
}
// We have either apples or oranges, so update the snapshot with those
snapshot.appendSections([.apples, .oranges])
snapshot.appendItems(apples, toSection: .apples)
snapshot.appendItems(oranges, toSection: .oranges)
}
/// Create our diffable data source
/// - Returns: Diffable data source
private func makeDataSource() -> DiffableViewDataSource {
return DiffableViewDataSource(tableView: tableView) { tableView, indexPath, item in
if let apple = item as? Apple {
//Apple
let cell = tableView.dequeueReusableCell(withIdentifier: "AppleCell", for: indexPath)
cell.textLabel?.text = "\(apple.name), core thickness: \(apple.coreThickness)mm"
return cell
} else if let orange = item as? Orange {
//Orange
let cell = tableView.dequeueReusableCell(withIdentifier: "OrangeCell", for: indexPath)
cell.textLabel?.text = "\(orange.name), peel thickness: \(orange.peelThickness)mm"
return cell
} else if let emptyData = item as? EmptyData {
//Empty
let cell = tableView.dequeueReusableCell(withIdentifier: "EmptyDataCell", for: indexPath)
cell.textLabel?.text = emptyData.emptyMessage
return cell
} else {
fatalError("Unknown cell type")
}
}
}
/// Subclass to help set up sections, etc.
class DiffableViewDataSource: UITableViewDiffableDataSource<Section, AnyHashable> {
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
//Use the snapshot to evaluate the section title
return snapshot().sectionIdentifiers[section].rawValue
}
}
}
/// Data to show if we have nothing returned from whatever API we use
struct EmptyData: Hashable {
let emptyMessage = "We're sorry! The fruit stand is closed due to inclement weather!"
let emptyImage = "cloud.bold.rain.fill"
}
/// One type of data
struct Apple: Hashable {
var name: String
var coreThickness: Int
}
/// Another type of data
struct Orange: Hashable {
var name: String
var peelThickness: Int
}
/// This will make debugging playground issues simpler
NSSetUncaughtExceptionHandler { exception in
print("Exception thrown: \(exception)")
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = DiffableTableViewController()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment