Create table view sections and an index list in iOS using UITableViewDiffableDataSource with NSDiffableDataSourceSnapshot. The diffable data source API replaces the old reloadData approach with a snapshot-based model. You describe the entire state of your data in a snapshot, and iOS automatically computes differences and animates changes. This eliminates index path out of bounds crashes and simplifies section management.
What Is UITableViewDiffableDataSource
UITableViewDiffableDataSource is a modern replacement for the UITableViewDataSource protocol. Apple introduced it in iOS 13 and has refined it through iOS 26. It uses NSDiffableDataSourceSnapshot to represent the complete state of your table view data. Each snapshot contains sections and items. When you apply a new snapshot, iOS compares it to the previous snapshot and automatically animates insertions, deletions, and reorders. This approach eliminates manual index path tracking and reduces animation glitches.
How to Set Up the Basic Data Source
Declare a data source property in your view controller:
var dataSource: UITableViewDiffableDataSource<String, String>!
Configure the data source in viewDidLoad:
func configureDataSource() {
dataSource = UITableViewDiffableDataSource<String, String>(
tableView: tableView
) { tableView, indexPath, animalName -> UITableViewCell? in
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = animalName
let imageFileName = animalName.lowercased().replacingOccurrences(of: " ", with: "_")
cell.imageView?.image = UIImage(named: imageFileName)
return cell
}
}
This closure provides the cell for each item. The generic parameters specify String for both section identifier and item identifier types.
How to Organize Data into Sections
Transform the flat array into a dictionary grouped by first letter:
var animalsDict = [String: [String]]()
var animalSectionTitles = [String]()
Build the dictionary in a helper method:
private func createAnimalDict() {
for animal in animals {
let firstLetterIndex = animal.index(animal.startIndex, offsetBy: 1)
let animalKey = String(animal[..<firstLetterIndex])
if var animalValues = animalsDict[animalKey] {
animalValues.append(animal)
animalsDict[animalKey] = animalValues
} else {
animalsDict[animalKey] = [animal]
}
}
animalSectionTitles = [String](animalsDict.keys)
animalSectionTitles.sort(by: { $0 < $1 })
}
Swift substring indexing extracts the first character. The String initializer converts the Substring result to a concrete string. Dictionary keys become section titles sorted alphabetically.
Call createAnimalDict in viewDidLoad. Update the snapshot method to use sections:
func updateSnapshot(animatingChange: Bool = false) {
var snapshot = NSDiffableDataSourceSnapshot<String, String>()
snapshot.appendSections(animalSectionTitles)
animalSectionTitles.forEach { section in
if let animals = animalsDict[section] {
snapshot.appendItems(animals, toSection: section)
}
}
dataSource.apply(snapshot, animatingDifferences: false)
}
The table view now has sections but no visible headers or index list.
How to Display Section Headers
Create a custom UITableViewDiffableDataSource subclass:
class AnimalTableDataSource: UITableViewDiffableDataSource<String, String> {
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.snapshot().sectionIdentifiers[section]
}
}
Update configureDataSource to return AnimalTableDataSource instead of the base class. Section headers now appear above each group.
How to Add the Index List
Override two methods in your custom data source:
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return self.snapshot().sectionIdentifiers
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return index
}
sectionIndexTitles returns the strings for the index bar. sectionForSectionIndexTitle tells the table view which section to jump to on tap. The index bar appears on the right side of the table view.
How to Display the Full A-Z Alphabet
Return the complete alphabet instead of only available sections:
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
}
Update the section mapping method to handle missing letters:
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
guard let index = self.snapshot().sectionIdentifiers.firstIndex(of: title) else {
return -1
}
return index
}
When a user taps a letter with no matching section, the method returns -1 and the table view stays at its current position.
How to Customize Section Header Appearance
Override willDisplayHeaderView in your data source:
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let headerView = view as! UITableViewHeaderFooterView
headerView.backgroundView?.backgroundColor = UIColor(red: 236/255, green: 240/255, blue: 241/255, alpha: 1.0)
headerView.textLabel?.textColor = UIColor(red: 231/255, green: 76/255, blue: 60/255, alpha: 1.0)
headerView.textLabel?.font = UIFont(name: "Avenir", size: 25.0)
}
willDisplayHeaderView fires just before the header renders. Customize background color, text color, and font to match your app design.
Best Practices for Sectioned Table Views
Keep sections meaningful. Grouping by first letter works for alphabetical data. For other datasets, group by date, category, or geographic region. Set adequate header heights between 40 and 50 points for comfortable tapping. Consider sticky headers that float at the top during scrolling. iOS defaults to sticky headers in plain table views. You can control this behavior with tableView.sectionHeaderTopPadding. Optimize large datasets with lazy-loading or paging. Diffable data source handles snapshots efficiently, but network-sourced data should include loading states.
Why Diffable Data Source Wins Over the Old Approach
The old UITableViewDataSource approach required implementing numberOfSections, numberOfRowsInSection, cellForRowAt, and manual change tracking. Diffable data source replaces all of that with a declarative snapshot model. You define sections and items in a snapshot, and iOS handles the rest. This eliminates index path out of bounds crashes and produces smooth animations automatically. Every iOS developer building table views in 2026 should default to UITableViewDiffableDataSource.
Summary
This tutorial covered the complete implementation process from setup to working code. You now have a functional implementation that you can extend and customize for your own projects. The same patterns apply to related features in the iOS ecosystem.
Frequently Asked Questions
What Xcode version do I need for this tutorial? This tutorial uses Xcode 26 with Swift 6.2. The code examples work with any recent version of Xcode that supports the frameworks used here.
What iOS version is required? This tutorial targets iOS 13. Most of the APIs shown work on earlier versions as well, but some newer APIs may require the version specified in the article.
What are the prerequisites for this tutorial? You need Xcode installed on a Mac and basic familiarity with Swift programming. No prior experience with the specific framework is required.