UITableView: základy, seznamy a buňky v iOS aplikacích

Autor: IT Sectr Publikováno: 2026-02-23 Doba čtení: 9 min

UITableView — hlavní komponenta UIKit pro zobrazování vertikálních seznamů v iOS. Každý řádek tabulky je buňka UITableViewCell s předdefinovanými styly. Vysvětlujeme základy UITableView: DataSource, delegát, opakované použití buněk a výkon tabulek v iOS aplikacích. Podle Apple (Human Interface Guidelines, 2026) zůstává UITableView nejpoužívanější UI komponentou v iOS — 85% aplikací z top-100 App Store obsahuje alespoň jednu tabulku. Pro složitá rozvržení si přečtěte článek o UICollectionView.

Hlavní body

  • UITableView — komponenta UIKit pro vertikální seznamy, dostupná od iOS 2.0 (iPhone OS 2).
  • UITableViewCell — buňka tabulky se čtyřmi vestavěnými styly a možností přizpůsobení.
  • UITableViewDataSource — protokol definující počet řádků a buněk pro každou sekci.
  • UITableViewDelegate — protokol pro zpracování výběru, úprav a nastavení výšky řádků.
  • UITableViewDiffableDataSource — moderní DataSource (iOS 13+) s automatickým výpočtem změn.

Co je UITableView v iOS?

UITableView — je třída z frameworku UIKit určená pro zobrazení rolovatelného seznamu řádků v jednom sloupci. Každý řádek je reprezentován objektem UITableViewCell. UITableView se objevil v iPhone OS 2 (2008) a od té doby zůstává hlavní komponentou pro seznamy v iOS. Architektura UITableView je postavena na vzoru MVC: data jsou spravována DataSource, vzhled a chování Delegate a zobrazení samotnou tabulkou.

Dva styly tabulky: .plain (souvislý seznam s volitelnými sekcemi) a .grouped (seskupené sekce se zaoblenými rohy a okraji). Od iOS 13 byl přidán .insetGrouped — grouped-styl s okraji od okrajů, používaný v aplikacích Nastavení a Zdraví. Výběr stylu ovlivňuje výchozí vzhled: plain-tabulky přichytí záhlaví sekce nahoru při rolování (sticky header), grouped — ne.

Self-Sizing Cells

Od iOS 8 UITableView podporuje buňky s automatickou velikostí (self-sizing cells). Pro aktivaci nastavte tableView.estimatedRowHeight (např. 80) a tableView.rowHeight = UITableView.automaticDimension. Tabulka automaticky vypočítá výšku řádku na základě omezení Auto Layout uvnitř buňky. Self-sizing snižuje výkon pro složité buňky — použijte pevnou výšku (rowHeight) pro tabulky s 500+ prvky.

Styly UITableViewCell: basic, subtitle, value1, value2

UITableViewCell nabízí čtyři vestavěné styly, které pokrývají 80% scénářů bez nutnosti vytvářet vlastní buňku. Každý styl se skládá z kombinace textLabel, detailTextLabel a imageView. Výběr stylu se nastavuje při inicializaci buňky pomocí init(style:reuseIdentifier:).

StyltextLabeldetailTextLabelimageViewPříklad
.default (.basic)Vlevo, tučněNeVolitelnéMenu, seznam položek
.subtitleVlevo, tučněPod textLabel, šedáVolitelnéKontakty, seznamy skladeb
.value1Vlevo, tučněVpravo, šedáNeNastavení s hodnotami
.value2Vpravo, modráVlevo, šedáNeTelefonní seznam (styl iOS 6)
swift
// Vytvoření tabulky s vlastními buňkami
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+) — moderní způsob konfigurace buněk pomocí contentConfiguration namísto přímého přístupu k textLabel/detailTextLabel. Použijte cell.defaultContentConfiguration() nebo vytvořte vlastní UIContentConfiguration. Výhody: podpora dynamického typu, tmavého motivu a VoiceOver hned po vybalení. Apple (WWDC 2024) doporučuje contentConfiguration pro všechny nové UITableView.

DataSource a Delegate: povinné metody

UITableViewDataSource — protokol, který poskytuje data pro tabulku. Povinné metody: tableView(_:numberOfRowsInSection:) a tableView(_:cellForRowAt:). Bez nich tabulka nemůže zobrazit žádný řádek. UITableViewDelegate — protokol pro správu vzhledu a chování: výška řádků, výběr, úpravy, contextual actions.

swift
// Příklad s více sekcemi a úpravami
extension ContactListController: UITableViewDelegate {

    // Vlastní výška pro každý řádek
    func tableView(_ tableView: UITableView,
                         heightForRowAt indexPath: IndexPath) -> CGFloat {
        80
    }

    // Zpracování výběru
    func tableView(_ tableView: UITableView,
                         didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let contact = contacts[indexPath.row]
        showDetail(for: contact)
    }

    // Přejetím smazat
    func tableView(_ tableView: UITableView,
                         trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
        -> UISwipeActionsConfiguration? {
        let deleteAction = UIContextualAction(style: .destructive, title: "Smazat") {
            [weak self] _, _, completion in
            self?.contacts.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
            completion(true)
        }
        return UISwipeActionsConfiguration(actions: [deleteAction])
    }
}

Výkon Delegate: metody velikosti (heightForRowAt) jsou volány pro každý viditelný řádek při každém rolování. Pro stejnou výšku nastavte přímo rowHeight — to je 10krát rychlejší než heightForRowAt. Pro self-sizing buňky nastavte estimatedRowHeight a automaticDimension — tabulka zavolá heightForRowAt pouze pro viditelné řádky a pro ostatní použije estimatedRowHeight.

Opakované použití buněk: fronta reuse a register

Fronta reuse — fond znovu použitelných UITableView buněk. Když buňka opustí obrazovku, není smazána, ale umístěna do fronty. Nové buňky nejsou vytvářeny od nuly — systém volá dequeueReusableCell(withIdentifier:for:), která vrátí buňku z fronty nebo vytvoří novou, pokud je fronta prázdná. To je klíčový mechanismus výkonu UITableView.

swift
// Vlastní buňka s ukládáním do mezipaměti
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)

        // Nastavení omezení Auto Layout
        avatarView.translatesAutoresizingMaskIntoConstraints = false
        // ... omezení ...
    }

    func configure(with contact: Contact) {
        nameLabel.text = contact.name
        subtitleLabel.text = contact.phone
        // Asynchronní načítání avatara
    }

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

// Registrace a použití
tableView.register(ContactCell.self, forCellReuseIdentifier: "contactCell")

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

prepareForReuse() — metoda volaná před umístěním buňky do fronty reuse. Resetujte celý stav buňky: text, obrázky, animace, načítání. Bez prepareForReuse může znovu použitá buňka zobrazovat stará data: předchozí obrázek bude viditelný na zlomek sekundy, dokud se nenačte nový. Apple (WWDC 2023) doporučuje čištění asynchronních operací pomocí URLSessionTask.cancel() v prepareForReuse.

Sekce, header a footer: seskupování dat

Sekce v UITableView seskupují logicky související řádky. Každá sekce může mít header (záhlaví) a footer (zápatí). Počet sekcí je určen metodou numberOfSections(in:) (výchozí 1). Pro styly grouped a insetGrouped jsou sekce vizuálně odděleny okraji a zaoblenými rohy.

Metoda DataSourcePopisNávratová hodnota
numberOfSectionsPočet sekcí (výchozí 1)Int
numberOfRowsInSectionPočet řádků v sekciInt
titleForHeaderInSectionText záhlaví sekceString?
titleForFooterInSectionText zápatí sekceString?
viewForHeaderInSectionVlastní zobrazení pro headerUIView?
heightForHeaderInSectionVýška headerCGFloat

Indexace: pro rychlou navigaci mezi sekcemi přidejte index — řetězec zobrazený vpravo. Implementujte sectionIndexTitles(for:) — vrací pole prvních písmen každé sekce. Pro 26+ sekcí to kriticky zlepšuje UX. V iOS 15+ tabulky podporují sectionHeaderTopPadding — mezeru mezi stavovým řádkem a prvním headerem, kterou lze resetovat na 0 pro kompaktní rozvržení.

Výkon UITableView: nejlepší postupy

Výkon UITableView je kritický pro aplikace s dlouhými seznamy. Hlavní problémy: pomalé rolování (jank) kvůli těžkým operacím v cellForRowAt, častá volání heightForRowAt, nedostatek prefetching. Apple (WWDC 2025) zdůrazňuje pět klíčových postupů pro tabulky s 1000+ prvky.

  • Flat-height — použijte konstantní rowHeight místo heightForRowAt pro všechny buňky. Rozdíl: 60 FPS vs 30 FPS pro tabulku s 500 řádky.
  • Prefetching — implementujte UITableViewDataSourcePrefetching pro asynchronní načítání dat (obrázky, API) před tím, než se buňka objeví na obrazovce.
  • Méně subview — každé subview v contentView zvyšuje dobu vykreslování. Použijte draw() pro jednoduchou grafiku (oddělovače, ikony) místo UIImageView.
  • Těžké operace na pozadí — formátování dat, lokalizaci, výpočty provádějte v modelu, ne v cellForRowAt. Použijte NSCache pro výsledky.
  • DiffableDataSource — nahraďte reloadData() za apply(snapshot) — animace probíhá zdarma, tabulka se znovu nenačítá celá.
swift
// Optimalizace pomocí 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)
        }
    }
}

// Použití DiffableDataSource pro plynulé aktualizace
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)
    }
}

Praktický výsledek: použití těchto optimalizací zvyšuje FPS rolování z 30 na 60 na zařízeních iPhone 12 a starších (podle testů Apple, 2025). V projektech IT Sectr používáme DiffableDataSource pro všechny nové tabulky a přidáváme prefetching pro tabulky s obrázky (avatary, náhledy, ikony aplikací).

Často kladené otázky

UITableView se neroluje — co dělat?

Zkontrolujte: (1) contentSize > frame.size — tabulka musí mít více obsahu než svou výšku; (2) tabulka není uvnitř jiného UIScrollView, který zachycuje gesta; (3) isScrollEnabled = true; (4) numberOfRowsInSection vrací správný počet. Typický problém — tabulka s heightForRowAt = UITableView.automaticDimension bez estimatedRowHeight, což vede k nekonečnému výpočtu výšky.

Jak aktualizovat UITableView bez blikání?

Použijte performBatchUpdates nebo DiffableDataSource místo reloadData(). reloadData() způsobí úplné znovunačtení bez animace, vytváří vizuální blikání. Pro bodovou aktualizaci použijte: reloadRows(at:with:), insertRows, deleteRows. Všechny změny uvnitř performBatchUpdates jsou animovány současně. DiffableDataSource to dělá automaticky bez ručních volání.

Jaký je rozdíl mezi UITableView a UICollectionView?

UITableView — jednosloupcový vertikální seznam s předdefinovanými styly buněk a vestavěnou editací. UICollectionView — flexibilní rozvržení (mřížka, seznam, karusel, kaskáda) s libovolným umístěním prvků. Zvolte UITableView pro jednoduché seznamy (nastavení, kontakty, zprávy). Zvolte UICollectionView pro galerie, katalogy, tabule a jakákoli nelineární rozvržení.

Jak odstranit prázdné řádky pod obsahem v UITableView?

Nastavte tableView.tableFooterView = UIView(frame: .zero). Ve výchozím nastavení UITableView zobrazuje prázdné řádky (oddělovače) pod posledním prvkem až ke spodnímu okraji. Nastavení prázdného UIView v tableFooterView je odstraní. Pro grouped-styl to není nutné — tabulka zobrazuje pouze skutečné řádky se zaoblenými rohy poslední sekce.

Jak vytvořit UITableView s konstantní výškou?

Nastavte tableView.rowHeight = 80 a tableView.estimatedRowHeight = 0 nebo vynechejte estimatedRowHeight. S pevnou výškou UITableView nevolá heightForRowAt, což urychluje vykreslování 5–10krát pro seznamy se 100+ prvky. Pro buňky s vlastní výškou (různý obsah) použijte self-sizing: estimatedRowHeight + UITableView.automaticDimension.

Shrnutí

  • UITableView — hlavní komponenta UIKit pro vertikální seznamy s architekturou DataSource a Delegate.
  • UITableViewCell — 4 vestavěné styly (.default, .subtitle, .value1, .value2) a vlastní přes contentConfiguration.
  • Fronta reuse — fond znovu použitelných buněk; register + dequeueReusableCell + prepareForReuse povinné.
  • DiffableDataSource (iOS 13+) — typově bezpečný DataSource s automatickým výpočtem změn a animací.
  • Výkon: pevná rowHeight, prefetching, méně subview, těžké operace na pozadí.
  • 85% iOS aplikací top-100 App Store používá UITableView pro zobrazení obsahu.
  • Pro mřížky, karusely a složitá rozvržení použijte UICollectionView místo UITableView.

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také