iOS
UITableView
Swift
Programming
Mobile Development

How to remove the last border of the last cell in UITableView?

Master System Design with Codemia

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

Introduction

In UITableView, the “last border” usually means the final separator line shown under the last visible cell. The cleanest solution depends on whether you want to hide only the table's trailing extra separators, hide the separator for the last real row, or replace the default separator behavior entirely with a custom view.

Remove Extra Empty Separators First

A common source of confusion is that the table may be showing separators for empty rows below your real data. That is not the same as the separator of the last real cell.

The standard fix is to give the table an empty footer view.

swift
1import UIKit
2
3final class ItemsViewController: UIViewController {
4    @IBOutlet private weak var tableView: UITableView!
5
6    override func viewDidLoad() {
7        super.viewDidLoad()
8        tableView.tableFooterView = UIView(frame: .zero)
9    }
10}

This removes separators for unused rows and is often enough to achieve the intended design.

Hide the Separator for the Last Real Cell

If you want the last actual cell to display without a bottom separator, you can adjust the separator inset in tableView(_:willDisplay:forRowAt:).

swift
1import UIKit
2
3final class ItemsViewController: UIViewController, UITableViewDelegate {
4    @IBOutlet private weak var tableView: UITableView!
5    private let items = ["A", "B", "C"]
6
7    override func viewDidLoad() {
8        super.viewDidLoad()
9        tableView.delegate = self
10    }
11
12    func tableView(_ tableView: UITableView,
13                   willDisplay cell: UITableViewCell,
14                   forRowAt indexPath: IndexPath) {
15        let lastRow = tableView.numberOfRows(inSection: indexPath.section) - 1
16
17        if indexPath.row == lastRow {
18            cell.separatorInset = UIEdgeInsets(top: 0,
19                                               left: 0,
20                                               bottom: 0,
21                                               right: .greatestFiniteMagnitude)
22        } else {
23            cell.separatorInset = tableView.separatorInset
24        }
25    }
26}

This effectively pushes the separator offscreen for the last row.

Reset the Separator for Reused Cells

Because table view cells are reused, you must restore normal separator settings for non-last rows. If you only configure the last row case and forget the default case, a reused cell can keep the “hidden separator” layout in the wrong position.

That is why the else branch above matters. Reuse bugs are one of the main reasons separator customization seems inconsistent.

A More Controlled Approach: Custom Separator Views

If the design is strict, the most reliable option is often to disable the default separator entirely and draw your own separator view inside the cell.

swift
tableView.separatorStyle = .none

Then configure the cell with a separator subview that you hide for the last row.

swift
1final class CustomCell: UITableViewCell {
2    let separator = UIView()
3
4    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
5        super.init(style: style, reuseIdentifier: reuseIdentifier)
6        separator.backgroundColor = .lightGray
7        contentView.addSubview(separator)
8        separator.translatesAutoresizingMaskIntoConstraints = false
9        NSLayoutConstraint.activate([
10            separator.heightAnchor.constraint(equalToConstant: 1),
11            separator.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
12            separator.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
13            separator.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
14        ])
15    }
16
17    required init?(coder: NSCoder) {
18        fatalError("init(coder:) has not been implemented")
19    }
20}

With this approach, you own the separator completely and can hide it per row during configuration.

Section Boundaries Matter

If the table has multiple sections, “last cell” can mean one of two things:

  • last row in each section
  • last row in the entire table

Most UI code means the former. Be explicit about the rule before writing the condition, especially if sections have different visual treatments.

Use tableFooterView when the issue is extra separators beyond your data. Use willDisplay or a custom separator when the issue is the last real row itself.

These are not interchangeable fixes, and many layout bugs happen because the wrong one is applied first.

Common Pitfalls

  • Confusing empty-row separators with the separator of the last real cell.
  • Hiding the separator for the last row but forgetting to reset it for reused cells.
  • Applying a “last row” condition without deciding whether it means per section or for the whole table.
  • Fighting the default separator system when a custom separator view would be simpler for the design.
  • Expecting tableFooterView to hide the separator of the last real row.

Summary

  • Use tableFooterView = UIView(frame: .zero) to remove separators for empty rows.
  • Use willDisplay and separator insets to hide the separator for the last real cell.
  • Reset separator settings for reused cells so layout does not bleed across rows.
  • If the design is strict, disable default separators and draw your own.
  • Decide whether “last cell” means last row in a section or last row in the whole table.

Course illustration
Course illustration

All Rights Reserved.