Ever wondered how Apple achieves that smooth, infinite scrolling effect in UIDatePicker? After seeing this Twitter post claiming the picker isn’t actually infinite and has an end, I decided to dig into the internals using LLDB to see how it really works. What I discovered was their surprisingly simple approach.

UIDatePicker Interface

The Architecture

UIDatePicker uses multiple UIPickerTableView instances - one for hours, one for minutes. Each tableview implements infinite scrolling using a classic “multiplier” technique rather than complex repositioning logic.

UIDatePicker TableView Structure

Key Findings

1. The Magic Number: 10,000 Rows

po [[dataSource] tableView:tableView numberOfRowsInSection:0]
0x0000000000002710  // 10,000 in hex

LLDB Debugging in Xcode

Apple uses exactly 10,000 rows for each wheel. This provides ~416 full cycles of 24 hours, which is more than sufficient for any practical use case.

2. Simple Modulo Math

The display logic is straightforward:

Hours: displayHour = (row % 24) + 1, then convert 24 to “00”

  • Row 0 → “01”
  • Row 15 → “16” (4 PM)
  • Row 23 → “00” (midnight)
  • Row 24 → “01” (cycles back to 1 AM)

Minutes: displayMinute = row % 60

  • Row 0 → “00”
  • Row 30 → “30” (half past)
  • Row 59 → “59”
  • Row 60 → “00” (cycles back to :00)

3. Smart Default Positioning

Rather than starting at row 0, Apple positions the scroll view around row ~5,000 by default. This centers the infinite scroll buffer, allowing equal scrolling in both directions.

The contentOffset reveals this: {0, 160318.33333333334} translates to approximately row 5,009.

Why This Works So Well

Memory Efficient: Only visible cells are created thanks to UITableView’s cell reusing. Despite 10,000 “rows,” memory usage remains constant.

Smooth Performance: No repositioning interruptions. Pure UITableView scrolling physics.

Practically Infinite: You’d need to scroll 30+ miles to reach an edge.

Simple Implementation: No complex geometry or custom scroll logic required.

Alternative Approaches Considered

During my experimentation, I tried several other approaches:

  1. Dynamic Repositioning: Reloading data when near edges, but this caused stuttering
  2. Circular Geometry: Custom pan gestures with trigonometry - worked but more complex
  3. Massive Multipliers: 100,000+ rows - unnecessary and wasteful

Apple’s 10,000 row approach strikes the perfect balance of simplicity and functionality.

Implementation Takeaway

The lesson here is that sometimes the “naive” solution is actually the best one. Instead of over-engineering with complex repositioning logic, Apple chose:

  • A reasonably large but not excessive buffer (10k rows)
  • Simple modulo arithmetic for display values
  • Standard UITableView behavior for smooth scrolling
  • Strategic default positioning in the center

This approach has served iOS well for over a decade, proving that elegant solutions often beat clever ones.

Code Example

Here’s how you might implement similar infinite scrolling:

class InfiniteTimePicker: UITableViewController {
    private let totalRows = 10000
    private let centerOffset = 5000
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return totalRows
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        let hour = (indexPath.row % 24) + 1
        let displayHour = hour == 24 ? 0 : hour
        cell.textLabel?.text = String(format: "%02d", displayHour)
        return cell
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let centerIndexPath = IndexPath(row: centerOffset, section: 0)
        tableView.scrollToRow(at: centerIndexPath, at: .middle, animated: false)
    }
}

Sometimes the best engineering solutions are the ones that don’t try to be too clever.