UITableViewの基礎:iOSアプリのリストとセル

著者: IT Sectr 公開日: 2026-02-23 読了時間: 9 分

UITableView — iOSで垂直リストを表示するためのUIKitの主要コンポーネントです。テーブルの各行は、定義済みスタイルを持つUITableViewCellです。UITableViewの基礎(DataSource、デリゲート、セルの再利用、iOSアプリでのテーブルパフォーマンス)について説明します。Apple(Human Interface Guidelines、2026)によると、UITableViewはiOSで最も使用されるUIコンポーネントであり続けており、App Storeトップ100の85%のアプリに少なくとも1つのテーブルが含まれています。複雑なレイアウトについては、UICollectionViewに関する記事をお読みください。

重要なポイント

  • UITableView — 垂直リスト用のUIKitコンポーネント。iOS 2.0(iPhone OS 2)から利用可能。
  • UITableViewCell — 4つの組み込みスタイルとカスタマイズオプションを備えたテーブルセル。
  • UITableViewDataSource — 各セクションの行数とセル数を定義するプロトコル。
  • UITableViewDelegate — 選択、編集、行の高さ設定を処理するプロトコル。
  • UITableViewDiffableDataSource — 変更を自動計算するモダンなDataSource(iOS 13+)。

iOSのUITableViewとは?

UITableViewは、1列でスクロール可能な行のリストを表示するために設計されたUIKitフレームワークのクラスです。各行はUITableViewCellオブジェクトで表されます。UITableViewはiPhone OS 2(2008)で初めて登場し、それ以来iOSのリストの主要コンポーネントであり続けています。UITableViewのアーキテクチャはMVCパターンに基づいており、データはDataSource、外観と動作はDelegate、表示はテーブル自体が管理します。

2つのテーブルスタイル.plain(オプションのセクションを持つ連続リスト)と.grouped(角丸とマージンを持つグループ化セクション)。iOS 13では.insetGroupedが追加されました — 端にマージンを持つグループ化スタイルで、設定とヘルスケアアプリで使用されています。スタイルの選択はデフォルトの外観に影響します。plainテーブルはスクロール時にセクションヘッダーを上部に固定します(スティッキーヘッダー)が、groupedテーブルは固定しません。

セルフサイジングセル

iOS 8以降、UITableViewはセルフサイジングセルをサポートしています。有効にするには、tableView.estimatedRowHeight(例:80)とtableView.rowHeight = UITableView.automaticDimensionを設定します。テーブルはセル内のAuto Layout制約に基づいて行の高さを自動計算します。セルフサイジングは複雑なセルではパフォーマンスが低下するため、500以上の要素があるテーブルでは固定高さ(rowHeight)を使用してください。

UITableViewCellのスタイル:Basic、Subtitle、Value1、Value2

UITableViewCellは、カスタムセルを作成しなくても80%のシナリオをカバーする4つの組み込みスタイルを提供します。各スタイルは、textLabel、detailTextLabel、imageViewの組み合わせで構成されます。スタイルはinit(style:reuseIdentifier:)でセルを初期化する際に選択します。

スタイルtextLabeldetailTextLabelimageView
.default (.basic)左、太字なしオプションメニュー、項目リスト
.subtitle左、太字textLabelの下、グレーオプション連絡先、プレイリスト
.value1左、太字右、グレーなし値付き設定
.value2右、青左、グレーなし電話帳(iOS 6スタイル)
swift
// カスタムセルでテーブルを作成
class ContactListController: UIViewController {

    private let tableView = UITableView(frame: .zero, style: .insetGrouped)

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.dataSource = self
        tableView.delegate = self
        tableView.rowHeight = 60
        view.addSubview(tableView)

        // Auto Layout
        tableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

extension ContactListController: UITableViewDataSource {

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

    func tableView(_ tableView: UITableView,
                         cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        let contact = contacts[indexPath.row]

        var content = cell.defaultContentConfiguration()
        content.text = contact.name
        content.secondaryText = contact.phone
        content.image = UIImage(systemName: "person.circle")
        cell.contentConfiguration = content

        return cell
    }
}

UIContentConfiguration(iOS 14+) — textLabel/detailTextLabelに直接アクセスする代わりに、contentConfigurationを介してセルを設定するモダンな方法です。cell.defaultContentConfiguration()を使用するか、カスタムのUIContentConfigurationを作成します。利点:Dynamic Type、ダークモード、VoiceOverの組み込みサポート。Apple(WWDC 2024)は、新しいUITableViewすべてにcontentConfigurationを推奨しています。

DataSourceとDelegate:必須メソッド

UITableViewDataSource — テーブルにデータを提供するプロトコル。必須メソッド:tableView(_:numberOfRowsInSection:)tableView(_:cellForRowAt:)。これらがないと、テーブルは1行も表示できません。UITableViewDelegate — 外観と動作を管理するプロトコル:行の高さ、選択、編集、コンテキストアクション。

swift
// 複数のセクションと編集の例
extension ContactListController: UITableViewDelegate {

    // 各行のカスタム高さ
    func tableView(_ tableView: UITableView,
                         heightForRowAt indexPath: IndexPath) -> CGFloat {
        80
    }

    // 選択処理
    func tableView(_ tableView: UITableView,
                         didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let contact = contacts[indexPath.row]
        showDetail(for: contact)
    }

    // Swipe-to-delete
    func tableView(_ tableView: UITableView,
                         trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
        -> UISwipeActionsConfiguration? {
        let deleteAction = UIContextualAction(style: .destructive, title: "削除") {
            [weak self] _, _, completion in
            self?.contacts.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
            completion(true)
        }
        return UISwipeActionsConfiguration(actions: [deleteAction])
    }
}

デリゲートのパフォーマンス:サイズメソッド(heightForRowAt)は、スクロールのたびに表示されている各行に対して呼び出されます。均一な高さの場合は、直接rowHeightを設定してください — これはheightForRowAtよりも10倍高速です。セルフサイジングセルの場合は、estimatedRowHeightとautomaticDimensionを設定します — テーブルは表示されている行に対してのみheightForRowAtを呼び出し、残りにはestimatedRowHeightを使用します。

セルの再利用:再利用キューと登録

再利用キュー — 再利用可能なUITableViewセルのプールです。セルが画面外にスクロールすると、削除されずにキューに入れられます。新しいセルはゼロから作成されず、システムはdequeueReusableCell(withIdentifier:for:)を呼び出し、キューからセルを返すか、キューが空の場合は新しいセルを作成します。これがUITableViewの主要なパフォーマンスメカニズムです。

swift
// キャッシュ付きカスタムセル
class ContactCell: UITableViewCell {

    private let avatarView = UIImageView()
    private let nameLabel = UILabel()
    private let subtitleLabel = UILabel()

    override init(style: UITableViewCell.Style, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupViews()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupViews() {
        contentView.addSubview(avatarView)
        contentView.addSubview(nameLabel)
        contentView.addSubview(subtitleLabel)

        // Auto Layout制約の設定
        avatarView.translatesAutoresizingMaskIntoConstraints = false
        // ... constraints ...
    }

    func configure(with contact: Contact) {
        nameLabel.text = contact.name
        subtitleLabel.text = contact.phone
        // アバターの非同期読み込み
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        avatarView.image = nil
        nameLabel.text = nil
        subtitleLabel.text = nil
    }
}

// 登録と使用
tableView.register(ContactCell.self, forCellReuseIdentifier: "contactCell")

// cellForRowAt内:
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactCell
cell.configure(with: contacts[indexPath.row])
return cell

prepareForReuse() — セルを再利用キューに入れる前に呼び出されるメソッドです。テキスト、画像、アニメーション、ローディング操作など、すべてのセルの状態をリセットしてください。prepareForReuseがないと、再利用されたセルが古いデータを表示する可能性があります:新しい画像がロードされるまで、前の画像が一瞬表示されます。Apple(WWDC 2023)は、prepareForReuseでURLSessionTask.cancel()を介して非同期操作をキャンセルすることを推奨しています。

セクション、ヘッダー、フッター:データのグループ化

セクションはUITableViewで論理的に関連する行をグループ化します。各セクションにはヘッダーとフッターを設定できます。セクション数はnumberOfSections(in:)メソッドで決定されます(デフォルトは1)。groupedおよびinsetGroupedスタイルでは、セクションはマージンと角丸で視覚的に区切られます。

DataSourceメソッド説明戻り値
numberOfSectionsセクション数(デフォルト1)Int
numberOfRowsInSectionセクション内の行数Int
titleForHeaderInSectionセクションヘッダーのタイトルテキストString?
titleForFooterInSectionセクションフッターのタイトルテキストString?
viewForHeaderInSectionヘッダー用のカスタムビューUIView?
heightForHeaderInSectionヘッダーの高さCGFloat

インデックス:セクション間のクイックナビゲーションのために、右側に表示される文字列の配列であるインデックスタイトルを追加します。sectionIndexTitles(for:)を実装すると、各セクションの最初の文字の配列が返されます。26以上のセクションがある場合、これによりUXが大幅に向上します。iOS 15+では、テーブルはsectionHeaderTopPaddingをサポートしています — ステータスバーと最初のヘッダーの間のパディングで、密集したレイアウトのために0にリセットできます。

UITableViewのパフォーマンス:ベストプラクティス

パフォーマンスは、長いリストを持つアプリにとってUITableViewのパフォーマンスが重要です。一般的な問題:cellForRowAtでの重い処理による遅いスクロール(ジャンク)、頻繁なheightForRowAt呼び出し、prefetchingの欠如。Apple(WWDC 2025)は、1000以上の要素を持つテーブルのための5つの主要なプラクティスを挙げています。

  • 固定高さ — すべてのセルにheightForRowAtの代わりに定数rowHeightを使用します。違い:500行のテーブルで60FPS対30FPS。
  • Prefetching — セルが画面に表示される前に非同期データローディング(画像、API)のためにUITableViewDataSourcePrefetchingを実装します。
  • サブビューを減らす — contentView内の各サブビューはレンダリング時間を増加させます。UIImageViewの代わりに、シンプルなグラフィック(区切り線、アイコン)にはdraw()を使用します。
  • バックグラウンドでの重い処理 — 日付の書式設定、ローカライゼーション、計算はcellForRowAtではなくモデルで行います。結果にはNSCacheを使用します。
  • DiffableDataSource — reloadData()をapply(snapshot)に置き換えます — アニメーションは無料で発生し、テーブル全体が再読み込みされることはありません。
swift
// Prefetchingによる最適化
extension ContactListController: UITableViewDataSourcePrefetching {

    func tableView(_ tableView: UITableView,
                         prefetchRowsAt indexPaths: [IndexPath]) {
        for indexPath in indexPaths {
            let contact = contacts[indexPath.row]
            ImageCache.shared.prefetch(url: contact.avatarUrl)
        }
    }

    func tableView(_ tableView: UITableView,
                         cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
        for indexPath in indexPaths {
            let contact = contacts[indexPath.row]
            ImageCache.shared.cancelPrefetch(url: contact.avatarUrl)
        }
    }
}

// スムーズな更新のためのDiffableDataSourceの使用
class ModernTableController: UIViewController {

    private var dataSource: UITableViewDiffableDataSource<Section, Contact>!

    func applyContacts(_ contacts: [Contact]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Contact>()
        snapshot.appendSections([.main])
        snapshot.appendItems(contacts)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

実用的な結果:これらの最適化を適用すると、iPhone 12以降のデバイスでスクロールFPSが30から60に向上します(Appleのテストデータ、2025年による)。IT Sectrのプロジェクトでは、すべての新しいテーブルにDiffableDataSourceを使用し、画像(アバター、プレビュー、アプリアイコン)を含むテーブルにはprefetchingを追加しています。

よくある質問

UITableViewがスクロールしない — どうすればいいですか?

確認事項:(1)contentSize > frame.size — テーブルは高さよりも多くのコンテンツを持つ必要があります。(2)テーブルがジェスチャーをインターセプトする別のUIScrollView内にないこと。(3)isScrollEnabled = true。(4)numberOfRowsInSectionが正しい数を返すこと。典型的な問題は、estimatedRowHeightなしでheightForRowAt = UITableView.automaticDimensionを使用しているテーブルで、無限の高さ計算を引き起こします。

UITableViewをちらつきなく更新するには?

reloadData()の代わりにperformBatchUpdatesまたはDiffableDataSourceを使用してください。reloadData()はアニメーションなしで完全に再読み込みを行い、視覚的なちらつきを引き起こします。対象を絞った更新には、reloadRows(at:with:)、insertRows、deleteRowsを使用します。performBatchUpdates内のすべての変更は同時にアニメーション化されます。DiffableDataSourceは手動呼び出しなしでこれを自動的に行います。

UITableViewとUICollectionViewの違いは?

UITableViewは、定義済みのセルスタイルと組み込みの編集機能を備えた単一カラムの垂直リストです。UICollectionViewは、柔軟なレイアウト(グリッド、リスト、カルーセル、ウォーターフォール)で要素を任意に配置できます。シンプルなリスト(設定、連絡先、メッセージ)にはUITableViewを選びます。ギャラリー、カタログ、ボード、および非線形レイアウトにはUICollectionViewを選びます。

UITableViewのコンテンツ下の空行を削除するには?

tableView.tableFooterView = UIView(frame: .zero)を設定します。デフォルトでは、UITableViewは最後のアイテムの下から下端まで空の行(区切り線)を表示します。空のUIViewをtableFooterViewとして設定すると、これらが削除されます。groupedスタイルの場合は不要です — テーブルは最後のセクションの角丸のみで実際の行のみを表示します。

UITableViewを一定の高さにするには?

tableView.rowHeight = 80tableView.estimatedRowHeight = 0を設定するか、estimatedRowHeightを省略します。固定高さの場合、UITableViewはheightForRowAtを呼び出さないため、100以上のアイテムがあるリストのレンダリングが5~10倍高速化されます。カスタム高さ(異なるコンテンツ)のセルには、セルフサイジング(estimatedRowHeight + UITableView.automaticDimension)を使用します。

まとめ

  • UITableView — DataSourceとDelegateアーキテクチャを備えた垂直リスト用の主要UIKitコンポーネント。
  • UITableViewCell — 4つの組み込みスタイル(.default、.subtitle、.value1、.value2)とcontentConfigurationによるカスタム。
  • 再利用キュー — 再利用可能なセルのプール。register + dequeueReusableCell + prepareForReuseが必須。
  • DiffableDataSource(iOS 13+) — 変更を自動計算しアニメーションを備えた型安全なDataSource。
  • パフォーマンス:固定rowHeight、prefetching、サブビュー削減、バックグラウンドでの重い処理。
  • App Storeトップ100の85%のiOSアプリがコンテンツ表示にUITableViewを使用しています。
  • グリッド、カルーセル、複雑なレイアウトには、UITableViewの代わりにUICollectionViewを使用します。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください