Skip to main content
April 1, 2026Noble Desktop Publishing Team/13 min read

Lists: Programming the Lists View Controller

Master iOS Table Views with Swift Programming

Essential iOS Table View Components

Data Source Protocol

Manages the data that populates your table view cells. Controls how many rows to display and what content appears in each cell.

Delegate Protocol

Handles user interactions with table view cells. Enables functionality like cell deletion, selection, and editing behaviors.

View Controller Integration

Connects the table view to your app's logic and data model. Acts as the coordinator between UI and data layers.

Topics Covered in This iOS Development Tutorial:

Master the essential components of iOS table view implementation: Data Source & Delegate Protocols for Table Views, strategic code organization using Mark Sections, writing robust methods that populate table views with dynamic data, implementing delegate methods for seamless cell deletion, creating responsive UI with add functionality through the Plus button, building smooth navigation flows with segues to secondary view controllers, and comprehensive testing strategies in the iOS Simulator

Exercise Preview

ex prev lists programming lists vc

Table View Implementation Process

1

Setup Protocols

Configure your View Controller to conform to UITableViewDataSource and UITableViewDelegate protocols

2

Connect Interface

Create visual connections between Table Views and View Controllers in the Storyboard

3

Implement Data Methods

Write numberOfRowsInSection and cellForRowAt methods to populate the table with data

4

Add User Interaction

Enable cell deletion and button functionality for complete user experience

Exercise Overview

In this comprehensive exercise, we'll architect the foundational code that powers your first View Controller's table view functionality—the cornerstone of modern iOS list-based interfaces. Table views remain one of the most critical UI components in iOS development, powering everything from settings screens to social media feeds in today's most successful apps.

To transform a static interface into a dynamic, data-driven experience, your View Controller must conform to the Table View's datasource protocol. We'll implement the two essential methods this protocol requires to render your table view seamlessly on-screen, following Apple's latest best practices for iOS 17+ compatibility.

Beyond basic display functionality, we'll elevate user interaction by implementing the Table View's delegate protocol. This includes crafting intuitive swipe-to-delete functionality that users expect from modern iOS apps, making your Plus (+) button fully functional for adding new entries, and establishing smooth navigation patterns through segues that maintain state and context between view controllers.

Getting Started

  1. Launch Xcode if it isn't already running. Ensure you're working with the latest stable version for optimal performance and access to current iOS development tools.

  2. If you completed the previous exercise, Lists.xcodeproj should remain open in your workspace. If you closed it, navigate back and reopen it to maintain your development context.

  3. We strongly recommend completing the prerequisite exercises (5A–5C) before proceeding, as they establish the essential UI foundation. If you haven't completed the previous exercises, follow these steps to access the starter project:

    • Navigate to File > Open in Xcode's menu bar.
    • Browse to Desktop > Class Files > yourname-iOS App Dev 2 Class > Lists Ready for Lists View Controller and double-click Lists.xcodeproj.
  4. Ensure both Main.storyboard and Data Model.swift are open in separate Editor tabs for efficient workflow management. Remember that Cmd–T creates new tabs, enabling you to work with multiple files simultaneously—a crucial skill for professional iOS development.

Pre-Development Setup

0/4

The Data Source & Delegate Protocols for Table Views

Understanding the separation of concerns between data source and delegate protocols is fundamental to mastering iOS table view architecture. Both View Controllers in our app feature table views as primary UI components, and their successful implementation requires a clear grasp of how these protocols interact with the iOS runtime system.

The data source protocol serves as the bridge between your app's data model and the visual representation users see. It's responsible for answering critical questions: How many rows should display? What content belongs in each cell? This protocol handles the "what" of your table view. Conversely, the delegate protocol manages user interaction and behavioral responses—the "how" of user engagement. When users swipe to delete, tap to select, or perform other gestures, the delegate protocol springs into action.

This architectural pattern, known as delegation, is pervasive throughout iOS development and represents Apple's commitment to clean, maintainable code structures. Modern iOS apps leverage these protocols to create responsive, data-driven interfaces that scale efficiently.

  1. Begin by establishing the visual connections between your table views and their respective View Controllers. Switch to the Main.storyboard tab to access Interface Builder.

  2. Ensure the Document Outline is visible for precise object selection. If it's hidden, locate the Show Document Outline button show hide document outline icon in the Editor's bottom-left corner and click it.

  3. Navigate to the Document Outline and locate the Lists Scene. If the scene hierarchy isn't expanded to reveal the Lists Table View within the superView, click on the middle controller labeled Lists, specifically where it displays Table View Prototype Content.

  4. Create the data source connection by holding Control and dragging from the Lists Table View to the Lists View Controller icon view controller icon in the Document Outline.

  5. When the connection popup appears, select dataSource. This establishes the View Controller as the authoritative source for table view data.

  6. Following iOS development best practices, the parent View Controller typically serves as both data source and delegate for optimal performance and code organization. Create the delegate connection by again Control-dragging from the Lists Table View to the Lists View Controller view controller icon.

  7. This time, select delegate from the popup menu.

  8. Replicate this process for the secondary View Controller to maintain consistency across your app's architecture:

    • Locate the List Items Scene in the Document Outline. If the Items Table View isn't visible, click on the corresponding table view in the Editor area.
    • Establish both connections by Control-dragging from the Items Table View to the List Items View Controller view controller icon.
    • Select dataSource for the first connection, then repeat the process and choose delegate.
  9. Now we'll implement the protocol conformance in code. Press Cmd–T to open a new tab, then select ListsVC.swift from the Project navigator.

  10. Update the class declaration to conform to both required protocols by adding the bold code to the class ListsVC line:

    import UIKit
    
    class ListsVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
       @IBOutlet weak var newListNameTextField: UITextField!
       @IBOutlet weak var listsTableView: UITableView!
  11. Notice the red error indicator red circle error icon that appears after adding the protocol conformance. Click it to see Xcode's message indicating that your View Controller doesn't yet implement the required dataSource protocol methods. This is expected behavior—we'll resolve this by implementing the mandatory methods next.

Protocol Connection Requirements

Both Table Views must be visually connected to their View Controllers through Control-drag operations. Each connection requires both dataSource and delegate selections to enable full functionality.

Data Source vs Delegate Protocols

FeatureData SourceDelegate
Primary FunctionManages data displayHandles user interactions
Required MethodsnumberOfRowsInSection, cellForRowAtcommit editingStyle
ControlsCell content and countEditing, selection, deletion
Recommended: Both protocols are essential for a fully functional table view implementation

How Mark Sections Keep Your Code Organized

Professional iOS development demands meticulous code organization, especially as projects scale beyond simple tutorials into production-ready applications. The MARK pragma directive represents one of Swift's most underutilized yet powerful organizational tools, transforming unwieldy view controller files into navigable, maintainable codebases.

Consider that a typical production iOS app's view controllers can contain hundreds or even thousands of lines of code. Without proper organization, finding specific methods becomes a time-consuming exercise in scrolling and searching. MARK sections create logical boundaries that mirror your app's functional architecture, making code reviews more efficient and onboarding new team members significantly easier.

  1. Establish clear functional boundaries within your View Controller by adding the MARK comments shown below. These create collapsible sections that group related functionality:

    override func viewDidLoad() {
          super.viewDidLoad()
       }
    
    
    // MARK: - TableView DataSource Methods
    
    // MARK: - TableView Delegate Methods
    
    // MARK: - UI Response Methods
    
       @IBAction func addListButtonTapped(_ sender: Any) {
    
       }

    The systematic organization we're implementing here mirrors patterns used in major iOS applications. Companies like Instagram, Airbnb, and Spotify rely on similar organizational strategies to manage complex view controllers that handle multiple responsibilities while maintaining code clarity and team collaboration efficiency.

  2. Experience the power of organized code navigation by clicking on the breadcrumb navigation. Above your code, to the right of ListsVC.swift, you'll see a clickable ListsVC identifier:

    lists click to see mark sections

  3. Clicking this identifier reveals Xcode's jump bar—a powerful navigation tool that displays your file's structure. Each MARK section appears with a distinctive section icon mark section icon, while individual methods display an M icon for easy identification:

    lists mark sections

    Experiment with clicking different sections, methods, or outlet variables (marked with a P for properties) to see how quickly you can navigate to specific code locations. This organizational approach becomes invaluable when working on larger projects or collaborating with development teams where quick code navigation directly impacts productivity.

Professional Code Organization

MARK comments create navigable sections in complex iOS files. Click next to the file name to see a structured popup with section icons and method markers (M for methods, P for properties).

MARK Section Structure

TableView DataSource methods

Contains numberOfRowsInSection and cellForRowAt methods that populate the table with data from your model.

TableView Delegate methods

Includes commit editingStyle and other interaction methods that handle user gestures and table modifications.

UI responsive methods

Houses IBAction methods like addListButtonTapped that respond to user interface interactions and button taps.

Writing Methods That Populate the Table View with Data

The table view data source protocol represents the fundamental contract between your data model and the iOS rendering system. Understanding these methods deeply is crucial because they're called frequently during the app lifecycle—every time the table view appears, refreshes, or updates its content. Efficient implementation directly impacts your app's performance and user experience.

The two required data source methods work in tandem: the first informs the system about quantity (how many rows to prepare), while the second handles quality (what content to display in each row). This separation allows iOS to optimize memory usage by only creating visible cells, recycling off-screen cells as users scroll—a pattern that enables smooth scrolling even with thousands of data items.

  1. Implement the row count method within your TableView DataSource methods section. Begin typing numberOfRows and select the numberOfRowsInSection autocomplete suggestion when it appears, then press Return:

    // MARK: - TableView DataSource Methods
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
          code
       }
    
    
    // MARK: - TableView Delegate Methods

    This method represents a critical performance checkpoint in your app. iOS calls it every time the table view needs to determine its content size, affecting everything from scroll indicator positioning to memory allocation strategies. Unlike older UITableViewController approaches, using a standard UIViewController with an embedded table view provides the flexibility needed for modern iOS app designs that combine multiple UI elements.

  2. Replace the placeholder code with a return statement that reflects your actual data count. The method must return the precise number of items in your data source:

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
       return lists.count 
    }

    The count property is a fundamental Array method that returns the current number of elements. This creates a dynamic relationship between your data model and UI—as users add or remove lists, the table view automatically adjusts its row count without requiring manual updates.

  3. To reinforce your understanding of the data flow, switch to the Data Model.swift tab and examine the lists array we're referencing in our data source method.

  4. Locate the global lists variable declaration at the bottom of the file:

    var lists = [List]() { didSet { lists.sort() { $0.title < $1.title } } }

    This sophisticated declaration demonstrates several advanced Swift concepts working together. The global scope ensures accessibility across view controllers, while the square bracket notation [List] creates a typed array that accepts only List class instances. The didSet property observer automatically maintains alphabetical sorting whenever the array changes, ensuring consistent UI presentation without manual intervention—a pattern that reduces bugs and improves user experience.

  5. Return to ListsVC.swift to implement the second required data source method that handles cell content population.

  6. Below your existing method, start typing cellForRowAt and select the highlighted autocomplete suggestion when it appears:

    lists cellForRowAt

  7. Xcode should generate the following method signature:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       code
    }
  8. This method represents the heart of table view rendering and deserves detailed understanding:

    • iOS calls this method in precise coordination with numberOfRowsInSection. After determining the total row count, the system requests individual cells only as they become visible, optimizing memory usage and scroll performance.
    • The indexPath parameter provides crucial positioning information, containing both section and row indices. While our current implementation uses a single section, understanding IndexPath prepares you for more complex table views with multiple sections—common in settings screens, contact lists, and other sophisticated interfaces.
    • The method executes once per visible cell, but iOS's cell reuse system means the same UITableViewCell instances get recycled as users scroll, dramatically reducing memory overhead in large datasets.
  9. Implement cell dequeuing by replacing the code placeholder. Type tableView.dequeue and select the appropriate autocomplete suggestion:

    lists dequeueReusableCell

  10. Your code should now include the dequeue method call:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       tableView.dequeueReusableCell(withIdentifier: String, for: IndexPath)
    }

    Cell dequeuing represents one of iOS's most elegant performance optimizations. Instead of creating new cell instances for every row, the system maintains a reuse pool of off-screen cells, dramatically reducing memory allocation overhead and garbage collection pressure—critical for smooth scrolling performance.

  11. Retrieve the exact cell identifier from your storyboard to ensure error-free connection. Switch to the Main.storyboard tab.

  12. Copy the cell's reuse identifier directly from Interface Builder to prevent typos:

    • Display the Utilities panel by clicking the Show Utilities button show hide utilities icon in Xcode's top-right corner.
    • In the Document Outline's Lists Scene, select the ListTitleTVCell. Expand the Table View hierarchy if necessary.
    • In the Attributes inspector attributes inspector icon, locate the Identifier field showing ListTitleTVCell. Copy this exact string.
    • Hide the Utilities panel show hide utilities icon to return to your standard workspace.
  13. Return to ListsVC.swift and complete the dequeue method call by replacing both placeholders:

    tableView.dequeueReusableCell(withIdentifier: "ListTitleTVCell", for: indexPath)

    The string identifier must match exactly what you defined in Interface Builder—any mismatch will cause runtime crashes. The indexPath parameter tells the dequeue system which specific row position this cell will occupy, enabling proper cell recycling and state management.

  14. Create a properly typed cell instance that provides access to your custom outlets and properties:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       let listTitleTVCell = tableView.dequeueReusableCell(withIdentifier: "ListTitleTVCell", for: indexPath)
       return listTitleTVCell
    }

    This resolves the compiler error because the returned object properly inherits from UITableViewCell, satisfying the method's return type requirement.

  15. Enable access to your custom cell's outlets by casting to the specific cell type. Add the type casting to your dequeue line:

    let listTitleTVCell = tableView.dequeueReusableCell(withIdentifier: "ListTitleTVCell", for: indexPath) as! ListTitleTVCell

    The force cast (as!) is appropriate here because we control the cell identifier and can guarantee the cast will succeed. This provides access to the custom outlets defined in your ListTitleTVCell class.

  16. Connect your data model to the visual interface by populating the cell's label with the corresponding list title:

    let listTitleTVCell = tableView.dequeueReusableCell(withIdentifier: "ListTitleTVCell", for: indexPath) as! ListTitleTVCell
    listTitleTVCell.listTitleLabel.text = lists[indexPath.row].title
    return listTitleTVCell
  17. Understanding this data binding pattern is crucial for iOS development:

    • The listTitleLabel represents one of your custom cell's IBOutlet connections, providing direct access to the UILabel component.
    • We access the appropriate data by using lists[indexPath.row].title—the array index corresponds directly to the table view row number, creating a one-to-one relationship between data and display.
    • The indexPath.row property extracts just the row number from the IndexPath, since we're working with a single-section table view.
    • This method executes for each visible cell, creating a dynamic binding between your data model and the user interface that updates automatically as data changes.
Why View Controller Over Table View Controller

Table View Controllers only display table views and cells, lacking flexibility. View Controllers allow custom UI elements like text fields and buttons above the table view.

Data Source Method Implementation

1

numberOfRowsInSection

Return lists.count to tell the table how many rows to display based on your data array

2

cellForRowAt Setup

Use tableView.dequeueReusableCell to efficiently reuse cells with the correct identifier

3

Cell Casting

Cast the cell as ListTitleTVCell to access custom outlet properties

4

Data Population

Set listTitleLabel.text to lists[indexPath.row].title to display the correct data

Writing a Delegate Method to Delete Table View Cells

Table view delegate methods transform static displays into interactive, user-responsive interfaces that meet modern iOS expectations. The ability to delete items through intuitive swipe gestures has become a fundamental user experience pattern, found in everything from email applications to social media platforms. Implementing deletion functionality properly requires understanding both the visual gesture recognition and the underlying data model synchronization.

Modern iOS users expect deletion to feel natural and immediate, with smooth animations and clear visual feedback. The delegate protocol provides the hooks needed to intercept user gestures and coordinate between the visual interface and your app's data layer, ensuring consistency and preventing the crashes that can occur when the UI and data model fall out of sync.

Model and UI Synchronization

When deleting cells, you must update both the data model and the UI. Remove from the lists array first, then call deleteRows with the fade animation to maintain consistency.

UITableViewCell EditingStyle Cases

Delete Case

Displays red Delete button when user swipes left on a cell. Most commonly used for removing items from lists.

Insert Case

Shows green plus button for adding new items. Less common but useful for inline item creation workflows.

Key Takeaways

1Table Views require both UITableViewDataSource and UITableViewDelegate protocols for complete functionality
2Visual connections between Table Views and View Controllers must be established through Control-drag operations in Storyboard
3MARK comments organize code into navigable sections, essential for managing complex iOS development files
4The numberOfRowsInSection method determines table size while cellForRowAt populates individual cells with data
5Cell deletion requires updating both the data model array and the table view UI to maintain synchronization
6dequeueReusableCell provides memory-efficient cell reuse by recycling off-screen cells for new content
7View Controllers offer more flexibility than Table View Controllers by allowing custom UI elements alongside table views
8IndexPath contains both section and row information, though single-section tables primarily use the row component

RELATED ARTICLES