UITableView: основе, листе и ћелије у iOS апликацијама

Аутор: IT Sectr Објављено: 2026-02-23 Време читања: 9 мин

UITableView — основни компонент UIKit за приказ вертикалних листи у iOS. Сваки ред табеле је ћелија UITableViewCell са предефинисаним стиловима. Објашњавамо основе UITableView: DataSource, делегат, поновно коришћење ћелија и перформансе табела у iOS апликацијама. Према Apple (Human Interface Guidelines, 2026), UITableView остаје најкоришћенији UI компонент у iOS — 85% апликација из топ-100 App Store садржи бар једну табелу. За сложене распореде прочитајте чланак о UICollectionView.

Главне ствари

  • UITableView — компонент UIKit за вертикалне листе, доступан од iOS 2.0 (iPhone OS 2).
  • UITableViewCell — ћелија табеле са четири уграђена стила и могућношћу прилагођавања.
  • UITableViewDataSource — протокол који одређује број редова и ћелија за сваку секцију.
  • UITableViewDelegate — протокол за обраду селекције, уређивања и подешавања висине редова.
  • UITableViewDiffableDataSource — савремени DataSource (iOS 13+) са аутоматским израчунавањем промена.

Шта је UITableView у iOS?

UITableView — је класа из frameworka UIKit намењена за приказ помичне листе редова у једној колони. Сваки ред је представљен објектом UITableViewCell. UITableView се појавио у iPhone OS 2 (2008) и од тада остаје главни компонент за листе у iOS. Архитектура UITableView је изграђена на MVC обрасцу: подацима управља DataSource, изгледом и понашањем Delegate, а приказом сама табела.

Два стила табеле: .plain (непрекидна листа са опционим секцијама) и .grouped (груписане секције са заобљеним угловима и размацима). Од iOS 13 додат је .insetGrouped — grouped-стил са размацима од ивица, који се користи у апликацијама Подешавања и Здравље. Избор стила утиче на подразумевани изглед: plain-табеле причвршћују header секције на врх при скроловању (sticky header), grouped — не.

Self-Sizing Cells

Од iOS 8, UITableView подржава ћелије са аутоматском величином (self-sizing cells). За укључивање поставите tableView.estimatedRowHeight (нпр. 80) и tableView.rowHeight = UITableView.automaticDimension. Табела аутоматски израчунава висину реда на основу Auto Layout ограничења унутар ћелије. Self-sizing смањује перформансе за сложене ћелије — користите фиксну висину (rowHeight) за табеле са 500+ елемената.

Стилови UITableViewCell: basic, subtitle, value1, value2

UITableViewCell нуди четири уграђена стила који покривају 80% сценарија без потребе за креирањем прилагођене ћелије. Сваки стил се састоји од комбинације 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+) — савремени начин подешавања ћелија кроз contentConfiguration уместо директног приступа textLabel/detailTextLabel. Користите cell.defaultContentConfiguration() или креирајте прилагођену UIContentConfiguration. Предности: подршка за динамички тип, тамну тему и VoiceOver из кутије. Apple (WWDC 2024) препоручује contentConfiguration за све нове UITableView.

DataSource и Delegate: обавезне методе

UITableViewDataSource — протокол који обезбеђује податке за табелу. Обавезне методе: tableView(_:numberOfRowsInSection:) и tableView(_:cellForRowAt:). Без њих табела неће моћи да прикаже ниједан ред. UITableViewDelegate — протокол за управљање изгледом и понашањем: висина редова, селекција, уређивање, contextual actions.

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)
    }

    // Превуци да избришеш
    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])
    }
}

Перформансе Delegate: методе величине (heightForRowAt) се позивају за сваки видљиви ред при сваком скроловању. За равномерну висину поставите rowHeight директно — ово је 10 пута брже од heightForRowAt. За self-sizing ћелије поставите estimatedRowHeight и automaticDimension — табела ће позвати heightForRowAt само за видљиве редове, а за остале користити estimatedRowHeight.

Поновно коришћење ћелија: reuse queue и register

Reuse queue — група поновно употребљивих 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
        // ... ограничења ...
    }

    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() — метода која се позива пре стављања ћелије у reuse queue. Ресетујте сво стање ћелије: текст, слике, анимације, учитавања. Без prepareForReuse, поновно коришћена ћелија може приказати старе податке: претходна слика ће бити видљива делић секунде док се не учита нова. Apple (WWDC 2023) препоручује чишћење асинхроних операција кроз URLSessionTask.cancel() у prepareForReuse.

Секције, header и footer: груписање података

Секције у UITableView групишу логички повезане редове. Свака секција може имати header (заглавље) и footer (подножје). Број секција одређује метода numberOfSections(in:) (подразумевано 1). За grouped и insetGrouped стилове, секције су визуелно одвојене размацима и заобљеним угловима.

DataSource методаОписПовратна вредност
numberOfSectionsБрој секција (подразумевано 1)Int
numberOfRowsInSectionБрој редова у секцијиInt
titleForHeaderInSectionТекст заглавља секцијеString?
titleForFooterInSectionТекст подножја секцијеString?
viewForHeaderInSectionПрилагођени приказ за headerUIView?
heightForHeaderInSectionВисина header-аCGFloat

Индексација: за брзу навигацију по секцијама додајте индекс — низ знакова приказан са десне стране. Имплементирајте sectionIndexTitles(for:) — враћа низ првих слова сваке секције. За 26+ секција ово критично побољшава UX. У iOS 15+, табеле подржавају sectionHeaderTopPadding — размак између статусне траке и првог header-а који се може ресетовати на 0 за компактан распоред.

Перформансе UITableView: најбоље праксе

Перформансе UITableView су критичне за апликације са дугачким листама. Главни проблеми: споро скроловање (jank) због тешких операција у cellForRowAt, чести позиви heightForRowAt, недостатак prefetching. Apple (WWDC 2025) истиче пет кључних пракси за табеле са 1000+ елемената.

  • Flat-height — користите константну rowHeight уместо heightForRowAt за све ћелије. Разлика: 60 FPS vs 30 FPS за табелу од 500 редова.
  • Prefetching — имплементирајте UITableViewDataSourcePrefetching за асинхроно учитавање података (слике, API) пре појављивања ћелије на екрану.
  • Мање subview-а — сваки subview у contentView повећава време рендеровања. Користите draw() за једноставну графику (сепаратори, иконе) уместо UIImageView.
  • Тешке операције у позадини — форматирање датума, локализацију, прорачуне обављајте у моделу, а не у 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)
    }
}

Практични резултат: примена ових оптимизација повећава FPS скроловања са 30 на 60 на уређајима iPhone 12 и старијим (према Apple тестовима, 2025). У IT Sectr пројектима користимо DiffableDataSource за све нове табеле и додајемо prefetching за табеле са сликама (аватари, прегледи, иконе апликација).

Често постављана питања

UITableView се не скролује — шта радити?

Проверите: (1) contentSize > frame.size — табела мора имати више садржаја од своје висине; (2) табела није унутар другог UIScrollView који пресреће гестове; (3) isScrollEnabled = true; (4) numberOfRowsInSection враћа тачан број. Типичан проблем — табела са heightForRowAt = UITableView.automaticDimension без estimatedRowHeight, што доводи до бесконачног израчунавања висине.

Како ажурирати UITableView без треперења?

Користите performBatchUpdates или DiffableDataSource уместо reloadData(). 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 = 80 и tableView.estimatedRowHeight = 0 или изоставите estimatedRowHeight. Са фиксном висином UITableView не позива heightForRowAt, што убрзава рендеровање 5–10 пута за листе са 100+ елемената. За ћелије са прилагођеном висином (различит садржај) користите self-sizing: estimatedRowHeight + UITableView.automaticDimension.

Закључак

  • UITableView — главни UIKit компонент за вертикалне листе са DataSource и Delegate архитектуром.
  • UITableViewCell — 4 уграђена стила (.default, .subtitle, .value1, .value2) и прилагођени кроз contentConfiguration.
  • Reuse queue — група поновно употребљивих ћелија; register + dequeueReusableCell + prepareForReuse обавезни.
  • DiffableDataSource (iOS 13+) — типно безбедан DataSource са аутоматским израчунавањем промена и анимацијом.
  • Перформансе: фиксна rowHeight, prefetching, мање subview-а, тешке операције у позадини.
  • 85% iOS апликација топ-100 App Store користи UITableView за приказ садржаја.
  • За мреже, каруселе и сложене распореде користите UICollectionView уместо UITableView.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође