UITableView — مؤلفه اصلی UIKit برای نمایش لیستهای عمودی در iOS است. هر سطر جدول یک سلول UITableViewCell با سبکهای از پیش تعیینشده است. مبانی UITableView را توضیح میدهیم: DataSource، delegate، استفاده مجدد از سلولها و عملکرد جداول در برنامههای iOS. طبق دادههای Apple (Human Interface Guidelines، 2026)، UITableView همچنان پرکاربردترین مؤلفه UI در iOS است — 85% از برنامههای top-100 App Store حداقل یک جدول دارند. برای چیدمانهای پیچیده مقاله UICollectionView را بخوانید.
نکات اصلی
UITableView — کلاسی از فریمورک UIKit است که برای نمایش لیست قابل پیمایش از سطرها در یک ستون طراحی شده است. هر سطر توسط یک شی UITableViewCell نمایش داده میشود. UITableView در iPhone OS 2 (2008) ظاهر شد و از آن زمان مؤلفه اصلی برای لیستها در iOS باقی مانده است. معماری UITableView بر اساس الگوی MVC ساخته شده است: دادهها توسط DataSource، ظاهر و رفتار توسط Delegate، و نمای توسط خود جدول مدیریت میشود.
دو سبک جدول: .plain (لیست پیوسته با بخشهای اختیاری) و .grouped (بخشهای گروهبندیشده با گوشههای گرد و فاصله). از iOS 13 به بعد .insetGrouped اضافه شده است — سبک grouped با فاصله از لبهها، که در برنامههای Settings و Health استفاده میشود. انتخاب سبک بر ظاهر پیشفرض تأثیر میگذارد: جداول plain هدر بخش را هنگام پیمایش به بالا میچسبانند (sticky header)، grouped — نمیچسبانند.
از iOS 8 به بعد، UITableView از سلولهای با اندازه خودکار (self-sizing cells) پشتیبانی میکند. برای فعالسازی tableView.estimatedRowHeight (مثلاً 80) و tableView.rowHeight = UITableView.automaticDimension را تنظیم کنید. جدول به طور خودکار ارتفاع سطر را بر اساس محدودیتهای Auto Layout داخل سلول محاسبه میکند. Self-sizing عملکرد را برای سلولهای پیچیده کاهش میدهد — برای جداول با 500+ عنصر از ارتفاع ثابت (rowHeight) استفاده کنید.
UITableViewCell چهار سبک داخلی ارائه میدهد که 80% سناریوها را بدون نیاز به ایجاد سلول سفارشی پوشش میدهد. هر سبک از ترکیبی از textLabel، detailTextLabel و imageView تشکیل شده است. انتخاب سبک هنگام مقداردهی اولیه سلول از طریق init(style:reuseIdentifier:) تنظیم میشود.
| سبک | textLabel | detailTextLabel | imageView | مثال |
|---|---|---|---|---|
| .default (.basic) | چپ، پررنگ | ندارد | اختیاری | منو، لیست موارد |
| .subtitle | چپ، پررنگ | زیر textLabel، خاکستری | اختیاری | مخاطبین، لیستهای پخش |
| .value1 | چپ، پررنگ | راست، خاکستری | ندارد | تنظیمات با مقادیر |
| .value2 | راست، آبی | چپ، خاکستری | ندارد | دفترچه تلفن (سبک iOS 6) |
// ایجاد جدول با سلولهای سفارشی
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 سفارشی ایجاد کنید. مزایا: پشتیبانی از نوع پویا، تم تاریک و VoiceOut از پیش فراهم شده است. Apple (WWDC 2024) contentConfiguration را برای همه UITableViewهای جدید توصیه میکند.
UITableViewDataSource — پروتکلی که دادههای جدول را فراهم میکند. روشهای اجباری: tableView(_:numberOfRowsInSection:) و tableView(_:cellForRowAt:). بدون آنها جدول نمیتواند هیچ سطری را نمایش دهد. UITableViewDelegate — پروتکل برای مدیریت ظاهر و رفتار: ارتفاع سطرها، انتخاب، ویرایش، contextual actions.
// مثال با چندین بخش و ویرایش
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 — استخری از سلولهای قابل استفاده مجدد UITableView. هنگامی که یک سلول از صفحه خارج میشود، حذف نمیشود بلکه در صف قرار میگیرد. سلولهای جدید از صفر ایجاد نمیشوند — سیستم dequeueReusableCell(withIdentifier:for:) را فراخوانی میکند که سلولی را از صف برمیگرداند یا اگر صف خالی باشد سلول جدیدی ایجاد میکند. این مکانیسم کلیدی عملکرد UITableView است.
// سلول سفارشی با کش کردن
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 فراخوانی میشود. تمام وضعیت سلول را بازنشانی کنید: متن، تصاویر، انیمیشنها، بارگذاریها. بدون prepareForReuse، سلول استفاده مجدد شده ممکن است دادههای قدیمی را نشان دهد: تصویر قبلی برای کسری از ثانیه قابل مشاهده خواهد بود تا زمانی که تصویر جدید بارگذاری شود. Apple (WWDC 2023) توصیه میکند عملیاتهای ناهمگام را از طریق URLSessionTask.cancel() در prepareForReuse پاک کنید.
بخشها در UITableView سطرهای مرتبط منطقی را گروهبندی میکنند. هر بخش میتواند header (عنوان) و footer (پاورقی) داشته باشد. تعداد بخشها توسط روش numberOfSections(in:) تعیین میشود (پیشفرض 1). برای سبکهای grouped و insetGrouped، بخشها به صورت بصری با فاصله و گوشههای گرد جدا میشوند.
| روش DataSource | توضیح | مقدار بازگشتی |
|---|---|---|
| numberOfSections | تعداد بخشها (پیشفرض 1) | Int |
| numberOfRowsInSection | تعداد سطرها در بخش | Int |
| titleForHeaderInSection | متن عنوان بخش | String? |
| titleForFooterInSection | متن پاورقی بخش | String? |
| viewForHeaderInSection | نمای سفارشی برای header | UIView? |
| heightForHeaderInSection | ارتفاع header | CGFloat |
نمایهسازی: برای پیمایش سریع بین بخشها، یک نمایه اضافه کنید — آرایهای از رشتهها که در سمت راست نمایش داده میشوند. sectionIndexTitles(for:) را پیادهسازی کنید — آرایهای از حروف اول هر بخش را برمیگرداند. برای 26+ بخش، این به طور بحرانی UX را بهبود میبخشد. در iOS 15+، جداول از sectionHeaderTopPadding پشتیبانی میکنند — فاصله بین نوار وضعیت و اولین header که میتواند برای چیدمان فشرده به 0 بازنشانی شود.
عملکرد UITableView برای برنامههای با لیستهای طولانی حیاتی است. مشکلات اصلی: پیمایش آهسته (jank) به دلیل عملیات سنگین در cellForRowAt، فراخوانیهای مکرر heightForRowAt، عدم وجود prefetching. Apple (WWDC 2025) پنج روش کلیدی را برای جداول با 1000+ عنصر برجسته میکند.
// بهینهسازی با 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 اضافه میکنیم.
سوالات متداول
بررسی کنید: (1) contentSize > frame.size — جدول باید محتوای بیشتری از ارتفاع خود داشته باشد؛ (2) جدول داخل UIScrollView دیگری که حرکات را رهگیری میکند نیست؛ (3) isScrollEnabled = true؛ (4) numberOfRowsInSection تعداد صحیح را برمیگرداند. مشکل معمول — جدول با heightForRowAt = UITableView.automaticDimension بدون estimatedRowHeight که منجر به محاسبه بینهایت ارتفاع میشود.
به جای reloadData() از performBatchUpdates یا DiffableDataSource استفاده کنید. reloadData() باعث بارگذاری مجدد کامل بدون انیمیشن میشود و چشمکزدن بصری ایجاد میکند. برای بهروزرسانی نقطهای از: reloadRows(at:with:)، insertRows، deleteRows استفاده کنید. تمام تغییرات داخل performBatchUpdates به طور همزمان انیمیشن میشوند. DiffableDataSource این کار را به طور خودکار بدون فراخوانی دستی انجام میدهد.
UITableView — لیست عمودی تکستونی با سبکهای سلول از پیش تعیینشده و ویرایش داخلی. UICollectionView — چیدمان انعطافپذیر (گرید، لیست، کاروسل، آبشاری) با قرارگیری دلخواه عناصر. UITableView را برای لیستهای ساده انتخاب کنید (تنظیمات، مخاطبین، پیامها). UICollectionView را برای گالریها، کاتالوگها، تابلوها و هر چیدمان غیرخطی انتخاب کنید.
tableView.tableFooterView = UIView(frame: .zero) را تنظیم کنید. به طور پیشفرض، UITableView سطرهای خالی (جداکنندهها) را در زیر آخرین عنصر تا لبه پایینی نمایش میدهد. تنظیم یک UIView خالی در tableFooterView آنها را حذف میکند. برای سبک grouped این مورد نیاز نیست — جدول فقط سطرهای واقعی را با گوشههای گرد آخرین بخش نمایش میدهد.
tableView.rowHeight = 80 و tableView.estimatedRowHeight = 0 را تنظیم کنید یا estimatedRowHeight را حذف کنید. با ارتفاع ثابت، UITableView heightForRowAt را فراخوانی نمیکند که رندر را برای لیستهای با 100+ عنصر 5–10 برابر سریعتر میکند. برای سلولهای با ارتفاع سفارشی (محتوا متفاوت) از self-sizing استفاده کنید: estimatedRowHeight + UITableView.automaticDimension.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.