iOS
TableViewController
Swift
app development
UI design

How to remove extra empty cells in TableViewController, iOS - Swift

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When a UITableView has fewer rows than the screen can display, it fills the remaining space with empty separator lines, creating "ghost" cells below your content. This is the default behavior of UITableView with the .plain style. The standard fix is setting tableFooterView to an empty UIView, which tells the table view to stop drawing separators after the last real cell. This one-line fix works in both UITableViewController and standalone UITableView setups.

The One-Line Fix

swift
1class ViewController: UITableViewController {
2
3    override func viewDidLoad() {
4        super.viewDidLoad()
5
6        // Remove empty cells below the last row
7        tableView.tableFooterView = UIView()
8    }
9
10    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
11        return 3  // Only 3 rows, but no ghost cells below
12    }
13
14    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
15        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
16        cell.textLabel?.text = "Row \(indexPath.row)"
17        return cell
18    }
19}

tableView.tableFooterView = UIView() sets a zero-height footer view. The table view stops drawing separator lines after the last cell because the footer view has no content and no separator.

Using a Standalone UITableView

swift
1class ViewController: UIViewController, UITableViewDataSource {
2
3    @IBOutlet weak var tableView: UITableView!
4
5    override func viewDidLoad() {
6        super.viewDidLoad()
7        tableView.dataSource = self
8        tableView.tableFooterView = UIView()  // Same fix
9    }
10
11    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
12        return items.count
13    }
14
15    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
16        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
17        cell.textLabel?.text = items[indexPath.row]
18        return cell
19    }
20}

The fix is identical whether you use UITableViewController or a regular UIViewController with a UITableView outlet.

Setting in Interface Builder

swift
1// You can also set it in the Storyboard:
2// 1. Select the UITableView in Interface Builder
3// 2. Set "Style" to "Grouped" — grouped style never shows empty cells
4
5// Or programmatically switch to grouped style
6let tableView = UITableView(frame: .zero, style: .grouped)

The .grouped table view style does not show empty cells by default because each section has its own background. However, grouped style adds section headers and footers with gray backgrounds, which changes the visual appearance.

Hiding Separators Entirely

swift
1override func viewDidLoad() {
2    super.viewDidLoad()
3
4    // Remove all separators
5    tableView.separatorStyle = .none
6
7    // Or set the footer view (removes only empty cell separators)
8    tableView.tableFooterView = UIView()
9}

Setting separatorStyle = .none removes all separator lines, including between real cells. Use this only if you want a completely separator-free design. The tableFooterView approach only removes separators below the last real cell.

swift
1override func viewDidLoad() {
2    super.viewDidLoad()
3
4    // Add a message when the table has content
5    let footer = UILabel()
6    footer.text = "End of list"
7    footer.textAlignment = .center
8    footer.textColor = .secondaryLabel
9    footer.font = .preferredFont(forTextStyle: .footnote)
10    footer.frame = CGRect(x: 0, y: 0, width: tableView.frame.width, height: 44)
11    tableView.tableFooterView = footer
12}

A custom footer view serves double duty: it removes empty cells and provides a visual indicator that the user has reached the end of the list.

SwiftUI Equivalent

swift
1import SwiftUI
2
3struct ContentView: View {
4    let items = ["Apple", "Banana", "Cherry"]
5
6    var body: some View {
7        List {
8            ForEach(items, id: \.self) { item in
9                Text(item)
10            }
11        }
12        .listStyle(.plain)
13        // SwiftUI List does not show extra empty rows by default
14        // But to remove extra separators in iOS 15+:
15        // .listRowSeparator(.hidden)
16    }
17}
18
19// iOS 16+ — hide separators below content
20struct CleanList: View {
21    let items = ["Apple", "Banana", "Cherry"]
22
23    var body: some View {
24        List {
25            ForEach(items, id: \.self) { item in
26                Text(item)
27                    .listRowSeparatorTint(.clear)
28            }
29        }
30        .scrollContentBackground(.hidden)
31    }
32}

SwiftUI's List does not show empty rows by default. Extra separators can be hidden with .listRowSeparator(.hidden) in iOS 15+.

Common Pitfalls

  • Setting footer on the wrong table view: In UITableViewController, use tableView.tableFooterView. If you have a custom UIViewController with an outlet, make sure the outlet is connected — setting the footer on a nil table view does nothing.
  • Grouped style changes appearance: Switching to .grouped removes empty cells but adds section header/footer spacing and a different background color. This may not be the visual effect you want.
  • Footer view height miscalculation: A UIView() with no frame has zero height, which is correct. If you set tableFooterView to a view with an explicit height, that space appears below the last cell.
  • Using separatorStyle = .none globally: This removes separators between real cells too, not just empty ones. If you want separators between cells but not below them, use tableFooterView = UIView() instead.
  • iOS version differences: In iOS 15+, UITableView with .plain style has a slightly different default appearance. The tableFooterView = UIView() fix still works, but test on your target iOS version.

Summary

  • Set tableView.tableFooterView = UIView() to remove extra empty cells — this is the standard one-line fix
  • Works with both UITableViewController and standalone UITableView
  • Alternatively, use .grouped table style, which never shows empty cells (but changes visual appearance)
  • Use separatorStyle = .none only if you want to hide all separators, including between real cells
  • SwiftUI List does not show empty rows by default

Course illustration
Course illustration

All Rights Reserved.