UIKit: Lists, Navigation & SwiftUI Interop
UITableView: Data Source & Delegate
class ItemListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
private let tableView = UITableView()
private var items: [Item] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "ItemCell")
}
// DataSource — supplies the content
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Reuse — recycles off-screen cells instead of allocating new ones,
// critical for scroll performance in long lists
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath)
cell.textLabel?.text = items[indexPath.row].title
return cell
}
// Delegate — handles interaction/appearance behavior
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
showDetail(for: items[indexPath.row])
}
}Diffable Data Source (Modern Alternative)
enum Section { case main }
var dataSource: UITableViewDiffableDataSource<Section, Item.ID>!
func configureDataSource() {
dataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath)
cell.textLabel?.text = self.items.first(where: { $0.id == itemID })?.title
return cell
}
}
// Instead of manual beginUpdates/insertRows/endUpdates bookkeeping,
// describe the desired state and let the diff + animation happen automatically
func applySnapshot(items: [Item], animating: Bool = true) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item.ID>()
snapshot.appendSections([.main])
snapshot.appendItems(items.map(\.id), toSection: .main)
dataSource.apply(snapshot, animatingDifferences: animating)
}Navigation & Presentation
// UINavigationController — push/pop stack navigation with automatic back button
navigationController?.pushViewController(DetailViewController(item: item), animated: true)
navigationController?.popViewController(animated: true)
// UITabBarController — top-level tab switching, each tab often its own nav stack
let tabBarController = UITabBarController()
tabBarController.viewControllers = [
UINavigationController(rootViewController: HomeViewController()),
UINavigationController(rootViewController: SettingsViewController()),
]
// Modal presentation
let detail = DetailViewController(item: item)
detail.modalPresentationStyle = .pageSheet
present(detail, animated: true)
// Dismiss
dismiss(animated: true)Bridging into SwiftUI
// UIViewRepresentable wraps a UIKit view for use inside SwiftUI
struct LegacyMapView: UIViewRepresentable {
@Binding var centerCoordinate: CLLocationCoordinate2D
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
// Called whenever relevant SwiftUI state changes
mapView.setCenter(centerCoordinate, animated: true)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
// Coordinator implements the UIKit delegate protocols the view needs
class Coordinator: NSObject, MKMapViewDelegate {
let parent: LegacyMapView
init(_ parent: LegacyMapView) { self.parent = parent }
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
parent.centerCoordinate = mapView.centerCoordinate
}
}
}
// Usage from SwiftUI
LegacyMapView(centerCoordinate: $coordinate)
.frame(height: 300)Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free